diff --git a/config/install/user.role.digital_signage_admin.yml b/config/install/user.role.digital_signage_admin.yml index 4c8f625..1c223c1 100644 --- a/config/install/user.role.digital_signage_admin.yml +++ b/config/install/user.role.digital_signage_admin.yml @@ -19,18 +19,24 @@ permissions: - 'add OpenY Digital Signage Schedule Item entities' - 'add OpenY Digital Signage Schedule entities' - 'add OpenY Digital Signage Screen entities' + - 'add digital signage playlist entities' + - 'add digital signage playlist item entities' - 'administer Digital Signage Classes Session entities' - 'administer Digital Signage Room entities' - 'administer OpenY Digital Signage Schedule Item entities' - 'administer OpenY Digital Signage Schedule entities' - 'administer OpenY Digital Signage Screen entities' - 'administer blocks' + - 'administer digital signage playlist entities' + - 'administer digital signage playlist item entities' - 'administer panelizer node screen_content content' - 'administer panelizer node screen_content layout' + - 'clone openy_digital_signage_playlist entity' - 'clone openy_digital_signage_sch_item entity' - 'clone openy_digital_signage_schedule entity' - 'clone openy_digital_signage_screen entity' - 'clone openy_ds_classes_session entity' + - 'clone openy_ds_playlist_item entity' - 'clone openy_ds_room entity' - 'create screen_content content' - 'delete Digital Signage Classes Session entities' @@ -39,6 +45,8 @@ permissions: - 'delete OpenY Digital Signage Schedule entities' - 'delete OpenY Digital Signage Screen entities' - 'delete any screen_content content' + - 'delete digital signage playlist entities' + - 'delete digital signage playlist item entities' - 'delete own screen_content content' - 'edit Digital Signage Classes Session entities' - 'edit Digital Signage Room entities' @@ -46,6 +54,8 @@ permissions: - 'edit OpenY Digital Signage Schedule entities' - 'edit OpenY Digital Signage Screen entities' - 'edit any screen_content content' + - 'edit digital signage playlist entities' + - 'edit digital signage playlist item entities' - 'edit own screen_content content' - 'revert screen_content revisions' - 'use text format digital_signage_html' @@ -55,4 +65,9 @@ permissions: - 'view OpenY Digital Signage Schedule Item entities' - 'view OpenY Digital Signage Schedule entities' - 'view OpenY Digital Signage Screen entities' + - 'view published digital signage playlist entities' + - 'view published digital signage playlist item entities' - 'view screen_content revisions' + - 'view the administration theme' + - 'view unpublished digital signage playlist entities' + - 'view unpublished digital signage playlist item entities' diff --git a/config/install/user.role.digital_signage_editor.yml b/config/install/user.role.digital_signage_editor.yml index 2c2d84f..75e2c29 100644 --- a/config/install/user.role.digital_signage_editor.yml +++ b/config/install/user.role.digital_signage_editor.yml @@ -21,9 +21,11 @@ permissions: - 'administer blocks' - 'administer panelizer node screen_content content' - 'administer panelizer node screen_content layout' + - 'clone openy_digital_signage_playlist entity' - 'clone openy_digital_signage_sch_item entity' - 'clone openy_digital_signage_schedule entity' - 'clone openy_ds_classes_session entity' + - 'clone openy_ds_playlist_item entity' - 'create screen_content content' - 'delete Digital Signage Classes Session entities' - 'delete OpenY Digital Signage Schedule Item entities' @@ -34,6 +36,8 @@ permissions: - 'edit OpenY Digital Signage Schedule Item entities' - 'edit OpenY Digital Signage Schedule entities' - 'edit any screen_content content' + - 'edit digital signage playlist entities' + - 'edit digital signage playlist item entities' - 'edit own screen_content content' - 'revert screen_content revisions' - 'use text format digital_signage_html' @@ -43,4 +47,9 @@ permissions: - 'view OpenY Digital Signage Schedule Item entities' - 'view OpenY Digital Signage Schedule entities' - 'view OpenY Digital Signage Screen entities' + - 'view published digital signage playlist entities' + - 'view published digital signage playlist item entities' - 'view screen_content revisions' + - 'view the administration theme' + - 'view unpublished digital signage playlist entities' + - 'view unpublished digital signage playlist item entities' diff --git a/modules/openy_digital_signage_alerts/config/install/core.entity_view_display.node.alert.ds_alert.yml b/modules/openy_digital_signage_alerts/config/install/core.entity_view_display.node.alert.ds_alert.yml new file mode 100644 index 0000000..d4cbc80 --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/core.entity_view_display.node.alert.ds_alert.yml @@ -0,0 +1,59 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.ds_alert + - field.field.node.alert.field_alert_belongs + - field.field.node.alert.field_alert_color + - field.field.node.alert.field_alert_description + - field.field.node.alert.field_alert_display_on_ds + - field.field.node.alert.field_alert_ds_description + - field.field.node.alert.field_alert_ds_screen + - field.field.node.alert.field_alert_icon_color + - field.field.node.alert.field_alert_link + - field.field.node.alert.field_alert_place + - field.field.node.alert.field_alert_text_color + - field.field.node.alert.field_alert_visibility_pages + - field.field.node.alert.field_alert_visibility_state + - field.field.node.alert.scheduled_status + - node.type.alert + module: + - link + - options + - text + - user +id: node.alert.ds_alert +targetEntityType: node +bundle: alert +mode: ds_alert +content: + field_alert_description: + weight: 0 + label: hidden + settings: { } + third_party_settings: { } + type: text_default + region: content + field_alert_ds_description: + type: text_default + weight: 1 + region: content + label: hidden + settings: { } + third_party_settings: { } +hidden: + addthis: true + content_moderation_control: true + field_alert_belongs: true + field_alert_color: true + field_alert_display_on_ds: true + field_alert_ds_screen: true + field_alert_icon_color: true + field_alert_link: true + field_alert_place: true + field_alert_text_color: true + field_alert_visibility_pages: true + field_alert_visibility_state: true + langcode: true + links: true + scheduled_status: true diff --git a/modules/openy_digital_signage_alerts/config/install/core.entity_view_mode.node.ds_alert.yml b/modules/openy_digital_signage_alerts/config/install/core.entity_view_mode.node.ds_alert.yml new file mode 100644 index 0000000..2e58c0c --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/core.entity_view_mode.node.ds_alert.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + module: + - node +id: node.ds_alert +label: 'DS Alert' +targetEntityType: node +cache: true diff --git a/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_display_on_ds.yml b/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_display_on_ds.yml new file mode 100644 index 0000000..890fbbf --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_display_on_ds.yml @@ -0,0 +1,25 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_alert_display_on_ds + - node.type.alert + enforced: + module: + - openy_digital_signage_alerts +id: node.alert.field_alert_display_on_ds +field_name: field_alert_display_on_ds +entity_type: node +bundle: alert +label: 'Display alert on a DS screen' +description: '' +required: false +translatable: false +default_value: + - + value: 0 +default_value_callback: '' +settings: + on_label: 'On' + off_label: 'Off' +field_type: boolean diff --git a/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_ds_description.yml b/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_ds_description.yml new file mode 100644 index 0000000..e6652a4 --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_ds_description.yml @@ -0,0 +1,31 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_alert_ds_description + - node.type.alert + enforced: + module: + - openy_digital_signage_alerts + module: + - allowed_formats + - text +third_party_settings: + allowed_formats: + plain_text: plain_text + full_html: '0' + digital_signage_html: '0' + digital_signage_inline_html: '0' + code: '0' +id: node.alert.field_alert_ds_description +field_name: field_alert_ds_description +entity_type: node +bundle: alert +label: 'DS description' +description: 'Alternative alert description specially for DS screens.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: text_long diff --git a/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_ds_screen.yml b/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_ds_screen.yml new file mode 100644 index 0000000..cce3280 --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/field.field.node.alert.field_alert_ds_screen.yml @@ -0,0 +1,27 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_alert_ds_screen + - node.type.alert + enforced: + module: + - openy_digital_signage_alerts +id: node.alert.field_alert_ds_screen +field_name: field_alert_ds_screen +entity_type: node +bundle: alert +label: Screen +description: 'Optionally you can specify the list DS screens to display this alert. Otherwise, the alert will be displayed on all screens.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:openy_digital_signage_screen' + handler_settings: + target_bundles: null + sort: + field: _none + auto_create: false +field_type: entity_reference diff --git a/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_display_on_ds.yml b/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_display_on_ds.yml new file mode 100644 index 0000000..193358d --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_display_on_ds.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - node + enforced: + module: + - openy_digital_signage_alerts +id: node.field_alert_display_on_ds +field_name: field_alert_display_on_ds +entity_type: node +type: boolean +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_ds_description.yml b/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_ds_description.yml new file mode 100644 index 0000000..ef4f1a4 --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_ds_description.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + module: + - node + - text + enforced: + module: + - openy_digital_signage_alerts +id: node.field_alert_ds_description +field_name: field_alert_ds_description +entity_type: node +type: text_long +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_ds_screen.yml b/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_ds_screen.yml new file mode 100644 index 0000000..2f90b42 --- /dev/null +++ b/modules/openy_digital_signage_alerts/config/install/field.storage.node.field_alert_ds_screen.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + module: + - node + - openy_digital_signage_screen + enforced: + module: + - openy_digital_signage_alerts +id: node.field_alert_ds_screen +field_name: field_alert_ds_screen +entity_type: node +type: entity_reference +settings: + target_type: openy_digital_signage_screen +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/openy_digital_signage_alerts/css/style.css b/modules/openy_digital_signage_alerts/css/style.css new file mode 100644 index 0000000..9c2466f --- /dev/null +++ b/modules/openy_digital_signage_alerts/css/style.css @@ -0,0 +1,13 @@ +.openy-digital-signage-block-static-ticker.ds-alert { + position: absolute; + width: 100%; + z-index: 1000; +} + +.ds-alert-header { + top: 0; +} + +.ds-alert-footer { + bottom: 0; +} diff --git a/modules/openy_digital_signage_alerts/js/screen-alerts.js b/modules/openy_digital_signage_alerts/js/screen-alerts.js new file mode 100644 index 0000000..b605361 --- /dev/null +++ b/modules/openy_digital_signage_alerts/js/screen-alerts.js @@ -0,0 +1,47 @@ +/** + * @file + * Provides OpenY Digital Signage alerts related behavior. + */ +(function ($, window, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Attaches the behavior to window object once. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Adds proper orientation classes to all the output layouts. + */ + Drupal.behaviors.ds_alerts = { + attach: function (context, settings) { + checkAlerts(settings.ds.screenId); + }, + }; + + /** + * Checks and redraw DS alerts. + * + * @param screenId + * DS screen ID. + */ + function checkAlerts(screenId) { + if (typeof screenId !== 'undefined' && screenId) { + $.ajax({ + url: "/ajax/screen-alerts/redraw-alert/" + screenId, + }).done(function(data) { + if (data) { + $('#openy-ds-alerts').replaceWith(data); + } + else { + $('#openy-ds-alerts').empty(); + } + }); + } + + // Set the function to infinite loop. + setTimeout(checkAlerts, 60000); + } + +})(jQuery, window, Drupal, drupalSettings); diff --git a/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.info.yml b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.info.yml new file mode 100644 index 0000000..fba658e --- /dev/null +++ b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.info.yml @@ -0,0 +1,8 @@ +name: Open Y Digital Signage Alerts +type: module +description: Digital Signage Alerts. +core: 8.x +package: "OpenY: Digital Signage" +dependencies: + - openy_digital_signage_screen + - openy_node_alert diff --git a/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.libraries.yml b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.libraries.yml new file mode 100644 index 0000000..6a6df1a --- /dev/null +++ b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.libraries.yml @@ -0,0 +1,10 @@ +ds_alerts: + version: VERSION + css: + theme: + css/style.css: {} + js: + js/screen-alerts.js: {} + dependencies: + - core/jquery + - core/drupalSettings diff --git a/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.module b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.module new file mode 100644 index 0000000..069a1c9 --- /dev/null +++ b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.module @@ -0,0 +1,98 @@ + [ + 'template' => 'node--alert--ds-alert', + 'base hook' => 'node' + ], + ]; +} + +/** + * Implements hook_help(). + */ +function openy_digital_signage_alerts_help($route_name, RouteMatchInterface $route_match) { + if ($route_name == 'help.page.openy_digital_signage') { + $output = '

' . t('About') . '

'; + $output .= '

' . t('The Open Y Digital Signage module allows you to display Open Y alerts on DS screens.') . '

'; + return $output; + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function openy_digital_signage_alerts_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) { + if ($form_id != 'node_alert_edit_form' && $form_id != 'node_alert_form') { + return; + } + + // Place DS settings to right side details block. + $form['openy_ds_settings'] = [ + '#type' => 'details', + '#title' => t('Digital Signage'), + '#open' => FALSE, + '#group' => 'advanced', + '#weight' => 120, + ]; + + $form['field_alert_ds_description']['#group'] = 'openy_ds_settings'; + $form['field_alert_ds_description']['widget'][0]['#format'] = 'plain_text'; + $form['field_alert_display_on_ds']['#group'] = 'openy_ds_settings'; + $form['field_alert_ds_screen']['#group'] = 'openy_ds_settings'; +} + +/** + * Implements hook_openy_digital_signage_screen_view_alter(). + */ +function openy_digital_signage_alerts_openy_digital_signage_screen_view_alter(&$build, OpenYScreenInterface $entity) { + if ($entity->getEntityTypeId() != 'openy_digital_signage_screen') { + return; + } + + /** @var Drupal\openy_digital_signage_alerts\DigitalSignatureAlertsManager $ds_alerts_manager */ + $ds_alerts_manager = \Drupal::service('openy_digital_signage_alerts.manager'); + $alerts = $ds_alerts_manager->getAlertsForScreen($entity); + if ($alerts) { + $build['alerts'] = $ds_alerts_manager->build($alerts); + $build['alerts']['#attached']['library'][] = 'openy_digital_signage_alerts/ds_alerts'; + $build['alerts']['#attached']['drupalSettings']['ds']['screenId'] = $entity->id(); + } +} + +/** + * Implements template_preprocess_node(). + */ +function openy_digital_signage_alerts_preprocess_node(&$variables) { + /** @var \Drupal\node\NodeInterface $node */ + $node = $variables['node']; + if ($node->bundle() != 'alert' || $variables['view_mode'] != 'ds_alert' ) { + return; + } + + // Alert can have alternative description specially for DS screens. + if (!$node->get('field_alert_ds_description')->isEmpty()) { + $variables['content']['field_alert_description'] = $variables['content']['field_alert_ds_description']; + } + else { + // We should use plain text for DS screens, so let's convert alert description. + $description = $node->field_alert_description->getString(); + $variables['content']['field_alert_description'] = MailFormatHelper::htmlToText($description); + } + + $variables['placement'] = $node->get('field_alert_place')->getString(); +} diff --git a/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.routing.yml b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.routing.yml new file mode 100644 index 0000000..26c8739 --- /dev/null +++ b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.routing.yml @@ -0,0 +1,10 @@ +screen.redraw_alert: + path: '/ajax/screen-alerts/redraw-alert/{screen}' + defaults: + _controller: '\Drupal\openy_digital_signage_alerts\Controller\AlertsController::checkAlerts' + options: + parameters: + screen: + type: entity:openy_digital_signage_screen + requirements: + _permission: 'access content' diff --git a/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.services.yml b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.services.yml new file mode 100644 index 0000000..e4bb177 --- /dev/null +++ b/modules/openy_digital_signage_alerts/openy_digital_signage_alerts.services.yml @@ -0,0 +1,4 @@ +services: + openy_digital_signage_alerts.manager: + class: Drupal\openy_digital_signage_alerts\DigitalSignatureAlertsManager + arguments: ['@entity_type.manager'] diff --git a/modules/openy_digital_signage_alerts/src/Controller/AlertsController.php b/modules/openy_digital_signage_alerts/src/Controller/AlertsController.php new file mode 100644 index 0000000..331795c --- /dev/null +++ b/modules/openy_digital_signage_alerts/src/Controller/AlertsController.php @@ -0,0 +1,68 @@ +alertsManager = $alerts_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('openy_digital_signage_alerts.manager') + ); + } + + /** + * Checks and render alerts for the screen. + * + * @param \Drupal\openy_digital_signage_screen\Entity\OpenYScreenInterface $screen + * The Digital Signage screen. + * + * @return \Symfony\Component\HttpFoundation\Response + * Response instance. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + public function checkAlerts(OpenYScreenInterface $screen) { + $response = new Response(); + + $alerts = $this->alertsManager->getAlertsForScreen($screen); + if ($alerts) { + $alerts = $this->alertsManager->build($alerts); + $data = render($alerts); + $response->setContent($data); + } + + return $response; + } + +} diff --git a/modules/openy_digital_signage_alerts/src/DigitalSignatureAlertsManager.php b/modules/openy_digital_signage_alerts/src/DigitalSignatureAlertsManager.php new file mode 100644 index 0000000..fcd046b --- /dev/null +++ b/modules/openy_digital_signage_alerts/src/DigitalSignatureAlertsManager.php @@ -0,0 +1,88 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * Gets alerts available for the specific screen. + * + * @param \Drupal\openy_digital_signage_screen\Entity\OpenYScreenInterface $screen + * The Digitanl Signage screen. + * + * @return array|\Drupal\Core\Entity\EntityInterface[] + * The array of alert nodes. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + public function getAlertsForScreen(OpenYScreenInterface $screen) { + $alerts = []; + + $storage = $this->entityTypeManager->getStorage('node'); + $query = $storage->getQuery(); + + $query->condition('type', 'alert'); + $query->condition('status', NodeInterface::PUBLISHED); + $query->condition('field_alert_display_on_ds', 1); + + $group = $query->orConditionGroup(); + $group->condition('field_alert_ds_screen', $screen->id()); + $group->notExists('field_alert_ds_screen'); + + $query->condition($group); + $nids = $query->execute(); + + if ($nids) { + $alerts = $storage->loadMultiple($nids); + } + + return $alerts; + } + + /** + * Builds DS alerts render array. + * + * @param NodeInterface[] $alerts + * The array of alert nodes. + * + * @return mixed + * Build array. + */ + public function build(array $alerts) { + $renderer = $this->entityTypeManager->getViewBuilder('node'); + $build = $renderer->viewMultiple($alerts, static::VIEW_MODE); + $build['#prefix'] = '
'; + $build['#suffix'] = '
'; + + return $build; + } + +} diff --git a/modules/openy_digital_signage_alerts/templates/node--alert--ds-alert.html.twig b/modules/openy_digital_signage_alerts/templates/node--alert--ds-alert.html.twig new file mode 100644 index 0000000..136090e --- /dev/null +++ b/modules/openy_digital_signage_alerts/templates/node--alert--ds-alert.html.twig @@ -0,0 +1,87 @@ +{# +/** + * @file + * Theme override to display a node. + * + * Available variables: + * - node: The node entity with limited access to object properties and methods. + * Only method names starting with "get", "has", or "is" and a few common + * methods such as "id", "label", and "bundle" are available. For example: + * - node.getCreatedTime() will return the node creation timestamp. + * - node.hasField('field_example') returns TRUE if the node bundle includes + * field_example. (This does not indicate the presence of a value in this + * field.) + * - node.isPublished() will return whether the node is published or not. + * Calling other methods, such as node.delete(), will result in an exception. + * See \Drupal\node\Entity\Node for a full list of public properties and + * methods for the node object. + * - label: The title of the node. + * - content: All node items. Use {{ content }} to print them all, + * or print a subset such as {{ content.field_example }}. Use + * {{ content|without('field_example') }} to temporarily suppress the printing + * of a given child element. + * - author_picture: The node author user entity, rendered using the "compact" + * view mode. + * - metadata: Metadata for this node. + * - date: Themed creation date field. + * - author_name: Themed author name field. + * - url: Direct URL of the current node. + * - display_submitted: Whether submission information should be displayed. + * - attributes: HTML attributes for the containing element. + * The attributes.class element may contain one or more of the following + * classes: + * - node: The current template type (also known as a "theming hook"). + * - node--type-[type]: The current node type. For example, if the node is an + * "Article" it would result in "node--type-article". Note that the machine + * name will often be in a short form of the human readable label. + * - node--view-mode-[view_mode]: The View Mode of the node; for example, a + * teaser would result in: "node--view-mode-teaser", and + * full: "node--view-mode-full". + * The following are controlled through the node publishing options. + * - node--promoted: Appears on nodes promoted to the front page. + * - node--sticky: Appears on nodes ordered above other non-sticky nodes in + * teaser listings. + * - node--unpublished: Appears on unpublished nodes visible only to site + * admins. + * - title_attributes: Same as attributes, except applied to the main title + * tag that appears in the template. + * - content_attributes: Same as attributes, except applied to the main + * content tag that appears in the template. + * - author_attributes: Same as attributes, except applied to the author of + * the node tag that appears in the template. + * - title_prefix: Additional output populated by modules, intended to be + * displayed in front of the main title tag that appears in the template. + * - title_suffix: Additional output populated by modules, intended to be + * displayed after the main title tag that appears in the template. + * - view_mode: View mode; for example, "teaser" or "full". + * - teaser: Flag for the teaser state. Will be true if view_mode is 'teaser'. + * - page: Flag for the full page state. Will be true if view_mode is 'full'. + * - readmore: Flag for more state. Will be true if the teaser content of the + * node cannot hold the main body content. + * - logged_in: Flag for authenticated user status. Will be true when the + * current user is a logged-in member. + * - is_admin: Flag for admin user status. Will be true when the current user + * is an administrator. + * + * @see template_preprocess_node() + */ +#} + +{% + set classes = [ + 'openy-digital-signage-block-static-ticker', + 'ds-alert', + 'ds-alert-' ~ placement|clean_class, +] +%} + +
+
+
+ +
+
+ {{ content.field_alert_description }} +
+
+
diff --git a/modules/openy_digital_signage_playlist/README.md b/modules/openy_digital_signage_playlist/README.md new file mode 100644 index 0000000..d837a0e --- /dev/null +++ b/modules/openy_digital_signage_playlist/README.md @@ -0,0 +1,5 @@ +## Module requirements + +This module require duration_field module. + +For correct work also add this [patch](https://www.drupal.org/files/issues/2018-06-08/2978206-2.patch) (see [drupal.org issue](https://www.drupal.org/node/2978206)). \ No newline at end of file diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.add.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.add.yml new file mode 100644 index 0000000..458bc2c --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.add.yml @@ -0,0 +1,32 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.openy_digital_signage_playlist.add + - field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.openy_digital_signage_playlist.add +targetEntityType: openy_digital_signage_playlist +bundle: openy_digital_signage_playlist +mode: add +content: + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + status: + type: boolean_checkbox + weight: 1 + region: content + settings: + display_label: true + third_party_settings: { } +hidden: + field_items: true + langcode: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.default.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.default.yml new file mode 100644 index 0000000..06f4a04 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.default.yml @@ -0,0 +1,50 @@ +langcode: en +status: true +dependencies: + config: + - field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.openy_digital_signage_playlist.default +targetEntityType: openy_digital_signage_playlist +bundle: openy_digital_signage_playlist +mode: default +content: + field_items: + type: entity_reference_playlist_items + weight: 6 + region: content + settings: { } + third_party_settings: { } + langcode: + type: language_select + weight: 2 + region: content + settings: + include_locked: true + third_party_settings: { } + name: + type: string_textfield + weight: -4 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + status: + type: boolean_checkbox + weight: -3 + region: content + settings: + display_label: true + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } +hidden: { } diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.edit.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.edit.yml new file mode 100644 index 0000000..5f05bf7 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_digital_signage_playlist.openy_digital_signage_playlist.edit.yml @@ -0,0 +1,24 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.openy_digital_signage_playlist.edit + - field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.openy_digital_signage_playlist.edit +targetEntityType: openy_digital_signage_playlist +bundle: openy_digital_signage_playlist +mode: edit +content: + field_items: + type: entity_reference_playlist_items + weight: 0 + region: content + settings: { } + third_party_settings: { } +hidden: + langcode: true + name: true + status: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_ds_playlist_item.openy_ds_playlist_item.default.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_ds_playlist_item.openy_ds_playlist_item.default.yml new file mode 100644 index 0000000..0c78822 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_form_display.openy_ds_playlist_item.openy_ds_playlist_item.default.yml @@ -0,0 +1,145 @@ +uuid: 1f973e34-5cad-4a26-8d6a-b100b34bbee6 +langcode: en +status: true +dependencies: + config: + - entity_browser.browser.digital_signage_images_library + module: + - datetime + - duration_field + - entity_browser + - field_group + - openy_digital_signage_playlist +third_party_settings: + field_group: + group_duration: + children: + - duration + parent_name: '' + weight: 5 + format_type: fieldset + format_settings: + label: Duration + description: '' + required_fields: true + id: '' + classes: container-inline + label: Duration + group_rotating_date: + children: + - date_start + - date_end + parent_name: '' + weight: 6 + format_type: fieldset + format_settings: + id: '' + classes: container-inline + description: '' + required_fields: true + label: 'Rotating Date' + group_display_time: + children: + - time_start + - time_end + parent_name: '' + weight: 7 + format_type: fieldset + format_settings: + id: '' + classes: container-inline + description: '' + required_fields: true + label: 'Display time' + group_playlist: + children: + - media + - playlist + parent_name: '' + weight: 3 + format_type: fieldset + format_settings: + label: Media + description: '' + required_fields: true + id: '' + classes: '' + label: Media +_core: + default_config_hash: uvh5xmP28p7HcbKXHlXVV9TxZ7nlPk4wcQSOMtcdf_w +id: openy_ds_playlist_item.openy_ds_playlist_item.default +targetEntityType: openy_ds_playlist_item +bundle: openy_ds_playlist_item +mode: default +content: + date_end: + type: datetime_default + weight: 5 + region: content + settings: { } + third_party_settings: { } + date_start: + type: datetime_default + weight: 4 + region: content + settings: { } + third_party_settings: { } + duration: + type: duration_widget + weight: 3 + region: content + settings: + duration: '' + third_party_settings: { } + media: + type: entity_browser_entity_reference + weight: 2 + settings: + entity_browser: digital_signage_images_library + field_widget_display: rendered_entity + field_widget_edit: true + field_widget_remove: true + open: true + selection_mode: selection_append + field_widget_display_settings: + view_mode: playlist_item_teaser + region: content + third_party_settings: { } + name: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + playlist: + type: entity_reference_autocomplete + weight: 3 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } + time_end: + type: datetime_time_only + weight: 6 + region: content + settings: { } + third_party_settings: { } + time_start: + type: datetime_time_only + weight: 5 + region: content + settings: { } + third_party_settings: { } + type: + type: options_select + weight: 1 + region: content + settings: { } + third_party_settings: { } +hidden: + langcode: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_form_mode.openy_digital_signage_playlist.add.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_form_mode.openy_digital_signage_playlist.add.yml new file mode 100644 index 0000000..1f935cb --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_form_mode.openy_digital_signage_playlist.add.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.add +label: Add +targetEntityType: openy_digital_signage_playlist +cache: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_form_mode.openy_digital_signage_playlist.edit.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_form_mode.openy_digital_signage_playlist.edit.yml new file mode 100644 index 0000000..906ee5b --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_form_mode.openy_digital_signage_playlist.edit.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.edit +label: Edit +targetEntityType: openy_digital_signage_playlist +cache: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.media.image.playlist_item_teaser.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.media.image.playlist_item_teaser.yml new file mode 100644 index 0000000..854e5fa --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.media.image.playlist_item_teaser.yml @@ -0,0 +1,36 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.playlist_item_teaser + - field.field.media.image.field_media_caption + - field.field.media.image.field_media_image + - field.field.media.image.field_media_in_library + - field.field.media.image.field_media_tags + - image.style.playlist_item_thumbnail + - media_entity.bundle.image + module: + - image +id: media.image.playlist_item_teaser +targetEntityType: media +bundle: image +mode: playlist_item_teaser +content: + field_media_image: + weight: 0 + label: hidden + settings: + image_style: playlist_item_thumbnail + image_link: '' + third_party_settings: { } + type: image + region: content +hidden: + created: true + field_media_caption: true + field_media_in_library: true + field_media_tags: true + langcode: true + name: true + thumbnail: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_digital_signage_playlist.openy_digital_signage_playlist.default.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_digital_signage_playlist.openy_digital_signage_playlist.default.yml new file mode 100644 index 0000000..8d0da3d --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_digital_signage_playlist.openy_digital_signage_playlist.default.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.openy_digital_signage_playlist.default +targetEntityType: openy_digital_signage_playlist +bundle: openy_digital_signage_playlist +mode: default +content: + field_items: + weight: 0 + label: above + settings: + link: true + view_mode: default + third_party_settings: { } + type: entity_reference_entity_view + region: content +hidden: + langcode: true + name: true + status: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_ds_playlist_item.openy_ds_playlist_item.default.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_ds_playlist_item.openy_ds_playlist_item.default.yml new file mode 100644 index 0000000..0414100 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_ds_playlist_item.openy_ds_playlist_item.default.yml @@ -0,0 +1,92 @@ +langcode: en +status: true +dependencies: + module: + - datetime + - duration_field + - openy_digital_signage_playlist + - options +id: openy_ds_playlist_item.openy_ds_playlist_item.default +targetEntityType: openy_ds_playlist_item +bundle: openy_ds_playlist_item +mode: default +content: + date_end: + label: above + type: datetime_default + weight: 5 + settings: + format_type: html_date + timezone_override: '' + region: content + third_party_settings: { } + date_start: + label: above + type: datetime_default + weight: 4 + settings: + format_type: html_date + timezone_override: '' + region: content + third_party_settings: { } + duration: + label: above + type: duration_time_display + weight: 3 + region: content + settings: { } + third_party_settings: { } + media: + label: above + type: entity_reference_entity_view + weight: 2 + settings: + view_mode: full_without_blazy + link: false + region: content + third_party_settings: { } + name: + label: above + type: string + weight: 0 + region: content + settings: + link_to_entity: false + third_party_settings: { } + playlist: + type: entity_reference_entity_view + weight: 8 + region: content + label: above + settings: + view_mode: default + link: false + third_party_settings: { } + time_end: + label: above + type: datetime_default + weight: 7 + settings: + format_type: html_time + timezone_override: '' + region: content + third_party_settings: { } + time_start: + label: above + type: datetime_default + weight: 6 + settings: + format_type: html_time + timezone_override: '' + region: content + third_party_settings: { } + type: + label: above + type: list_default + weight: 1 + region: content + settings: { } + third_party_settings: { } +hidden: + langcode: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_ds_playlist_item.openy_ds_playlist_item.teaser.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_ds_playlist_item.openy_ds_playlist_item.teaser.yml new file mode 100644 index 0000000..96cb4b2 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_view_display.openy_ds_playlist_item.openy_ds_playlist_item.teaser.yml @@ -0,0 +1,95 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.openy_ds_playlist_item.teaser + module: + - datetime + - duration_field + - openy_digital_signage_playlist + - options +id: openy_ds_playlist_item.openy_ds_playlist_item.teaser +targetEntityType: openy_ds_playlist_item +bundle: openy_ds_playlist_item +mode: teaser +content: + date_end: + type: datetime_default + weight: 6 + region: content + label: hidden + settings: + timezone_override: '' + format_type: html_date + third_party_settings: { } + date_start: + type: datetime_default + weight: 5 + region: content + label: hidden + settings: + timezone_override: '' + format_type: html_date + third_party_settings: { } + duration: + type: duration_human_display + weight: 3 + region: content + label: hidden + settings: + text_length: short + separator: space + third_party_settings: { } + media: + label: hidden + type: entity_reference_entity_view + weight: 2 + settings: + view_mode: playlist_item_teaser + link: false + region: content + third_party_settings: { } + name: + label: hidden + type: string + weight: 0 + region: content + settings: + link_to_entity: false + third_party_settings: { } + playlist: + type: entity_reference_label + weight: 4 + region: content + label: hidden + settings: + link: true + third_party_settings: { } + time_end: + type: datetime_default + weight: 8 + region: content + label: hidden + settings: + timezone_override: '' + format_type: html_time + third_party_settings: { } + time_start: + type: datetime_default + weight: 7 + region: content + label: hidden + settings: + timezone_override: '' + format_type: html_time + third_party_settings: { } + type: + label: hidden + type: list_default + weight: 1 + region: content + settings: { } + third_party_settings: { } +hidden: + langcode: true + uid: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_view_mode.media.playlist_item_teaser.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_view_mode.media.playlist_item_teaser.yml new file mode 100644 index 0000000..6afa6f1 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_view_mode.media.playlist_item_teaser.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + module: + - media_entity +id: media.playlist_item_teaser +label: 'Playlist item teaser' +targetEntityType: media +cache: true diff --git a/modules/openy_digital_signage_playlist/config/install/core.entity_view_mode.openy_ds_playlist_item.teaser.yml b/modules/openy_digital_signage_playlist/config/install/core.entity_view_mode.openy_ds_playlist_item.teaser.yml new file mode 100644 index 0000000..374b9dc --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/core.entity_view_mode.openy_ds_playlist_item.teaser.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: + module: + - openy_digital_signage_playlist +id: openy_ds_playlist_item.teaser +label: Teaser +targetEntityType: openy_ds_playlist_item +cache: true diff --git a/modules/openy_digital_signage_playlist/config/install/field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items.yml b/modules/openy_digital_signage_playlist/config/install/field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items.yml new file mode 100644 index 0000000..07011fa --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/field.field.openy_digital_signage_playlist.openy_digital_signage_playlist.field_items.yml @@ -0,0 +1,25 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.openy_digital_signage_playlist.field_items + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.openy_digital_signage_playlist.field_items +field_name: field_items +entity_type: openy_digital_signage_playlist +bundle: openy_digital_signage_playlist +label: Items +description: '' +required: true +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:openy_ds_playlist_item' + handler_settings: + target_bundles: null + sort: + field: _none + auto_create: false +field_type: entity_reference diff --git a/modules/openy_digital_signage_playlist/config/install/field.storage.openy_digital_signage_playlist.field_items.yml b/modules/openy_digital_signage_playlist/config/install/field.storage.openy_digital_signage_playlist.field_items.yml new file mode 100644 index 0000000..c566b72 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/field.storage.openy_digital_signage_playlist.field_items.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - openy_digital_signage_playlist +id: openy_digital_signage_playlist.field_items +field_name: field_items +entity_type: openy_digital_signage_playlist +type: entity_reference +settings: + target_type: openy_ds_playlist_item +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/openy_digital_signage_playlist/config/install/image.style.playlist_item_thumbnail.yml b/modules/openy_digital_signage_playlist/config/install/image.style.playlist_item_thumbnail.yml new file mode 100644 index 0000000..fb94f22 --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/image.style.playlist_item_thumbnail.yml @@ -0,0 +1,13 @@ +langcode: en +status: true +dependencies: { } +name: playlist_item_thumbnail +label: 'Playlist item thumbnail' +effects: + 4afb63bd-af41-488d-b557-c4d87c8db4b0: + uuid: 4afb63bd-af41-488d-b557-c4d87c8db4b0 + id: image_scale_and_crop + weight: 1 + data: + width: 150 + height: 75 diff --git a/modules/openy_digital_signage_playlist/config/install/views.view.playlists.yml b/modules/openy_digital_signage_playlist/config/install/views.view.playlists.yml new file mode 100644 index 0000000..b455e9f --- /dev/null +++ b/modules/openy_digital_signage_playlist/config/install/views.view.playlists.yml @@ -0,0 +1,589 @@ +langcode: en +status: true +dependencies: + config: + - system.menu.admin + module: + - openy_digital_signage_playlist +id: playlists +label: Playlists +module: views +description: '' +tag: '' +base_table: openy_digital_signage_playlist +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access digital signage playlist overview' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: true + caption: '' + summary: '' + description: '' + columns: + name: name + uid: uid + status: status + changed: changed + operations: operations + info: + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + uid: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + operations: + align: '' + separator: '' + empty_column: false + responsive: '' + default: '-1' + empty_table: false + row: + type: fields + fields: + name: + id: name + table: openy_digital_signage_playlist + field: name + relationship: none + group_type: group + admin_label: '' + label: Title + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: openy_digital_signage_playlist + entity_field: name + plugin_id: field + uid: + id: uid + table: openy_digital_signage_playlist + field: uid + relationship: none + group_type: group + admin_label: '' + label: Author + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: false + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: openy_digital_signage_playlist + entity_field: uid + plugin_id: field + status: + id: status + table: openy_digital_signage_playlist + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: custom + format_custom_true: Published + format_custom_false: Unpublished + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: openy_digital_signage_playlist + entity_field: status + plugin_id: field + changed: + id: changed + table: openy_digital_signage_playlist + field: changed + relationship: none + group_type: group + admin_label: '' + label: Modified + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: openy_digital_signage_playlist + entity_field: changed + plugin_id: field + operations: + id: operations + table: openy_digital_signage_playlist + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + entity_type: openy_digital_signage_playlist + plugin_id: entity_operations + filters: + name: + id: name + table: openy_digital_signage_playlist + field: name + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: name_op + label: Title + description: '' + use_operator: false + operator: name_op + identifier: name + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + contributor: '0' + editor: '0' + blog_author: '0' + digital_signage_editor: '0' + digital_signage_admin: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: openy_digital_signage_playlist + entity_field: name + plugin_id: string + status: + id: status + table: openy_digital_signage_playlist + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: All + group: 1 + exposed: true + expose: + operator_id: '' + label: Status + description: '' + use_operator: false + operator: status_op + identifier: status + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + contributor: '0' + editor: '0' + blog_author: '0' + digital_signage_editor: '0' + digital_signage_admin: '0' + is_grouped: true + group_info: + label: Status + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + entity_type: openy_digital_signage_playlist + entity_field: status + plugin_id: boolean + sorts: { } + title: Playlists + header: { } + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'You do not have any playlists at the moment. Please create first playlist' + plugin_id: text_custom + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } + page: + display_plugin: page + id: page + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: admin/digital-signage/playlist + menu: + type: normal + title: Playlists + description: 'Combine slides and images into a playlist, set slide duration with the option to link a playlist to a screen.' + expanded: false + parent: system.admin_openy_digital_signage + weight: 0 + context: '0' + menu_name: admin + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } diff --git a/modules/openy_digital_signage_playlist/css/playlist-control.css b/modules/openy_digital_signage_playlist/css/playlist-control.css new file mode 100644 index 0000000..5539524 --- /dev/null +++ b/modules/openy_digital_signage_playlist/css/playlist-control.css @@ -0,0 +1,180 @@ +/* Define our icon font, which is generated from the SVGs in /images. */ +@font-face { + font-family: OpenYDSPlaylistFont; + src: url(../fonts/icons.woff); +} + +#playlist-control-tray { + bottom: -30px; + left: 0; + position: fixed; + text-align: center; + transition: bottom 0.4s; + width: 100%; + z-index: 100; + -webkit-transition: bottom 0.4s; +} + +#playlist-control-tray:hover { + bottom: 0; +} + +.playlist-control-tab-wrapper {} + +.playlist-control-tabs { + list-style: none; + margin: 0; + padding: 0; +} + +.playlist-control-tabs-content {} + +.playlist-control-tab { + overflow: hidden; + position: relative; + display: inline-block; + vertical-align: bottom; + margin-bottom: -1px; + background-color: white; + border-top: 1px solid darkgray; + box-shadow: 3px -1px 2px 0 rgba(0, 0, 0, 0.3333); +} + +.playlist-control-tab:first-child { + border-left: 1px solid darkgray; + border-top-left-radius: 5px; + box-shadow: -2px -1px 2px rgba(0, 0, 0, 0.3333); + right: -5px; +} + +.playlist-control-tab:last-child { + border-right: 1px solid darkgray; + border-top-right-radius: 5px; + box-shadow: 2px -1px 2px rgba(0, 0, 0, 0.3333); +} + +.playlist-control-button { + font-size: 13px; + padding: 5px 10px 7px 10px; + text-transform: capitalize; + color: black; + display: block; + vertical-align: top; + border: none; + cursor: pointer; + transition: .2s; + transition-property: color, border-color; + border-bottom: 1px solid darkgray; + transition: border-bottom 0.4s, padding-bottom 0.4s; + -webkit-transition: border-bottom 0.4s, padding-bottom 0.4s; +} + +.playlist-control-button:hover { + padding-bottom: 10px; + color: rgb(67, 125, 33); + border: none; + border-bottom: 3px solid rgb(67, 125, 33); +} + +.playlist-control-button--disabled { + color: lightgrey !important; +} + +.playlist-control-icon { + display: inline-block; + vertical-align: middle; + font-family: OpenYDSPlaylistFont; + font-size: 24px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.playlist-control-icon--edit:before { + content: "\e904"; +} +.playlist-control-icon--play2:before { + content: "\ea15"; +} +.playlist-control-icon--pause:before { + content: "\ea16"; +} +.playlist-control-icon--stop:before { + content: "\ea17"; +} +.playlist-control-icon--previous:before { + content: "\ea18"; +} +.playlist-control-icon--next:before { + content: "\ea19"; +} +.playlist-control-icon--backward:before { + content: "\ea1a"; +} +.playlist-control-icon--forward2:before { + content: "\ea1b"; +} +.playlist-control-icon--play3:before { + content: "\ea1c"; +} +.playlist-control-icon--pause2:before { + content: "\ea1d"; +} +.playlist-control-icon--stop2:before { + content: "\ea1e"; +} +.playlist-control-icon--backward2:before { + content: "\ea1f"; +} +.playlist-control-icon--forward3:before { + content: "\ea20"; +} +.playlist-control-icon--first:before { + content: "\ea21"; +} +.playlist-control-icon--last:before { + content: "\ea22"; +} +.playlist-control-icon--previous2:before { + content: "\ea23"; +} +.playlist-control-icon--next2:before { + content: "\ea24"; +} +.playlist-control-icon--eject:before { + content: "\ea25"; +} +.playlist-control-icon--volume-high:before { + content: "\ea26"; +} +.playlist-control-icon--volume-medium:before { + content: "\ea27"; +} +.playlist-control-icon--volume-low:before { + content: "\ea28"; +} +.playlist-control-icon--volume-mute:before { + content: "\ea29"; +} +.playlist-control-icon--volume-mute2:before { + content: "\ea2a"; +} +.playlist-control-icon--volume-increase:before { + content: "\ea2b"; +} +.playlist-control-icon--volume-decrease:before { + content: "\ea2c"; +} +.playlist-control-icon--loop:before { + content: "\ea2d"; +} +.playlist-control-icon--loop2:before { + content: "\ea2e"; +} +.playlist-control-icon--infinite:before { + content: "\ea2f"; +} +.playlist-control-icon--shuffle:before { + content: "\ea30"; +} + +.playlist-control-tab-title {} diff --git a/modules/openy_digital_signage_playlist/css/playlist.css b/modules/openy_digital_signage_playlist/css/playlist.css new file mode 100644 index 0000000..73d4ceb --- /dev/null +++ b/modules/openy_digital_signage_playlist/css/playlist.css @@ -0,0 +1,22 @@ +.playlist-item { + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: cover; + display: none; + font-size: 100px; + height: 100vh; + left: 0; + position: absolute; + top: 0; + width: 100vw; + z-index: 1; +} + +.playlist-item--active { + display: block; +} + +.playlist-item--activating { + display: block; + z-index: 10; +} diff --git a/modules/openy_digital_signage_playlist/css/playlist_items_widget.css b/modules/openy_digital_signage_playlist/css/playlist_items_widget.css new file mode 100644 index 0000000..4c1031b --- /dev/null +++ b/modules/openy_digital_signage_playlist/css/playlist_items_widget.css @@ -0,0 +1,101 @@ +/** + * @file + * Styles customization for playlist items widget. + */ +.playlist-details { + margin-bottom: 40px; +} +.playlist-details .playlist-actions { + float: right; +} +.playlist-details .playlist-actions a{ + margin-left: 25px; +} +.field--name-field-items .field-multiple-table td { + padding: 3px 12px; +} +.field--name-field-items .tabledrag-toggle-weight-wrapper { + display: none; +} +.field--name-field-items .openy_ds_playlist_item { + float: left; + width: 85%; +} +.field--name-field-items .openy_ds_playlist_item .item-row { + display: table; +} +.field--name-field-items .openy_ds_playlist_item .item-col { + display: table-cell; + text-align: center; + vertical-align:middle; + padding: 0 15px; +} +.field--name-field-items .openy_ds_playlist_item .item-col.text-value { + font-size: 16px; + width: 20%; +} +.field--name-field-items .openy_ds_playlist_item .teaser-image .empty-image-wrapper{ + display: block; + height: 75px; + width: 150px; + background: #5c9bc1; + text-align: center; + line-height: 75px; + font-size: 24px; + color: #FFFFFF; +} +.field--name-field-items .openy_ds_playlist_item .teaser-image .field--name-media { + width: 150px; +} +.field--name-field-items .openy_ds_playlist_item .text-value .empty-date-time { + display: block; + text-align: center; +} +.field--name-field-items tr.item-expires_soon { + background: #EF9A9A; +} +.field--name-field-items tr.item-expired { + background: #E0E0E0 +} +.field--name-field-items .item-actions { + float: right; + line-height: 75px; +} +.field--name-field-items .item-actions a.button { + display: block; + margin: 10px; +} +.field--name-field-items .item-actions .ajax-progress-throbber { + display: none; +} +.media.media-image.view-mode-playlist-item-teaser { + float: left; +} +.field--name-field-items .field-multiple-table .header-row { + width: 100%; + display: table; + float: right; + text-align: right; +} +.field--name-field-items .field-multiple-table .header-col { + display: table-cell; + text-align: center; + vertical-align:middle; + padding: 0 15px; + width: 15%; +} +.field--name-field-items .field-multiple-table .header-col:first-child { + width: 12%; +} + +@media (max-width: 1540px) { + .field--name-field-items .openy_ds_playlist_item { + width: 85%; + } +} + +@media (max-width: 840px) { + .field--name-field-items .openy_ds_playlist_item { + width: 100%; + } +} \ No newline at end of file diff --git a/modules/openy_digital_signage_playlist/fonts/icons.woff b/modules/openy_digital_signage_playlist/fonts/icons.woff new file mode 100644 index 0000000..90ace49 Binary files /dev/null and b/modules/openy_digital_signage_playlist/fonts/icons.woff differ diff --git a/modules/openy_digital_signage_playlist/fonts/selection.json b/modules/openy_digital_signage_playlist/fonts/selection.json new file mode 100644 index 0000000..307226b --- /dev/null +++ b/modules/openy_digital_signage_playlist/fonts/selection.json @@ -0,0 +1 @@ +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M128 736v160h160l471.893-471.893-160-160-471.893 471.893zM883.627 300.373c16.64-16.64 16.64-43.52 0-60.16l-99.84-99.84c-16.64-16.64-43.52-16.64-60.16 0l-78.080 78.080 160 160 78.080-78.080z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["tab_edit"],"grid":0},"attrs":[],"properties":{"order":3,"id":11,"prevSize":32,"code":59652,"name":"tab_edit"},"setIdx":0,"setId":3,"iconIdx":11},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416zM384 288l384 224-384 224z"],"tags":["play","player"],"defaultCode":59925,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"play2, player","name":"play2","order":11,"id":278,"prevSize":32,"code":59925},"setIdx":2,"setId":1,"iconIdx":277},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416zM320 320h128v384h-128zM576 320h128v384h-128z"],"tags":["pause","player"],"defaultCode":59926,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"pause, player2","name":"pause","order":12,"id":279,"prevSize":32,"code":59926},"setIdx":2,"setId":1,"iconIdx":278},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416zM320 320h384v384h-384z"],"tags":["stop","player"],"defaultCode":59927,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"stop, player3","name":"stop","order":13,"id":280,"prevSize":32,"code":59927},"setIdx":2,"setId":1,"iconIdx":279},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416z","M448 512l256-192v384z","M320 320h128v384h-128v-384z"],"tags":["previous","player"],"defaultCode":59928,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"previous, player4","name":"previous","order":14,"id":281,"prevSize":32,"code":59928},"setIdx":2,"setId":1,"iconIdx":280},{"icon":{"paths":["M512 0c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 928c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416z","M576 512l-256-192v384z","M704 320h-128v384h128v-384z"],"tags":["next","player"],"defaultCode":59929,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"next, player5","name":"next","order":15,"id":282,"prevSize":32,"code":59929},"setIdx":2,"setId":1,"iconIdx":281},{"icon":{"paths":["M512 1024c282.77 0 512-229.23 512-512s-229.23-512-512-512-512 229.23-512 512 229.23 512 512 512zM512 96c229.75 0 416 186.25 416 416s-186.25 416-416 416-416-186.25-416-416 186.25-416 416-416zM704 672l-224-160 224-160zM448 672l-224-160 224-160z"],"tags":["backward","player"],"defaultCode":59930,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"backward, player6","name":"backward","order":16,"id":283,"prevSize":32,"code":59930},"setIdx":2,"setId":1,"iconIdx":282},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 928c-229.75 0-416-186.25-416-416s186.25-416 416-416 416 186.25 416 416-186.25 416-416 416zM320 352l224 160-224 160zM576 352l224 160-224 160z"],"tags":["forward","player"],"defaultCode":59931,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"forward2, player7","name":"forward2","order":17,"id":284,"prevSize":32,"code":59931},"setIdx":2,"setId":1,"iconIdx":283},{"icon":{"paths":["M192 128l640 384-640 384z"],"tags":["play","player"],"defaultCode":59932,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"play3, player8","name":"play3","order":2,"id":285,"prevSize":32,"code":59932},"setIdx":2,"setId":1,"iconIdx":284},{"icon":{"paths":["M128 128h320v768h-320zM576 128h320v768h-320z"],"tags":["pause","player"],"defaultCode":59933,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"pause2, player9","name":"pause2","order":3,"id":286,"prevSize":32,"code":59933},"setIdx":2,"setId":1,"iconIdx":285},{"icon":{"paths":["M128 128h768v768h-768z"],"tags":["stop","player","square"],"defaultCode":59934,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"stop2, player10","name":"stop2","order":4,"id":287,"prevSize":32,"code":59934},"setIdx":2,"setId":1,"iconIdx":286},{"icon":{"paths":["M576 160v320l320-320v704l-320-320v320l-352-352z"],"tags":["backward","player"],"defaultCode":59935,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"backward2, player11","name":"backward2","order":5,"id":288,"prevSize":32,"code":59935},"setIdx":2,"setId":1,"iconIdx":287},{"icon":{"paths":["M512 864v-320l-320 320v-704l320 320v-320l352 352z"],"tags":["forward","player"],"defaultCode":59936,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"forward3, player12","name":"forward3","order":6,"id":289,"prevSize":32,"code":59936},"setIdx":2,"setId":1,"iconIdx":288},{"icon":{"paths":["M128 896v-768h128v352l320-320v320l320-320v704l-320-320v320l-320-320v352z"],"tags":["first","player"],"defaultCode":59937,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"first, player13","name":"first","order":7,"id":290,"prevSize":32,"code":59937},"setIdx":2,"setId":1,"iconIdx":289},{"icon":{"paths":["M896 128v768h-128v-352l-320 320v-320l-320 320v-704l320 320v-320l320 320v-352z"],"tags":["last","player"],"defaultCode":59938,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"last, player14","name":"last","order":8,"id":291,"prevSize":32,"code":59938},"setIdx":2,"setId":1,"iconIdx":290},{"icon":{"paths":["M256 896v-768h128v352l320-320v704l-320-320v352z"],"tags":["previous","player"],"defaultCode":59939,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"previous2, player15","name":"previous2","order":9,"id":292,"prevSize":32,"code":59939},"setIdx":2,"setId":1,"iconIdx":291},{"icon":{"paths":["M768 128v768h-128v-352l-320 320v-704l320 320v-352z"],"tags":["next","player"],"defaultCode":59940,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"next2, player16","name":"next2","order":10,"id":293,"prevSize":32,"code":59940},"setIdx":2,"setId":1,"iconIdx":292},{"icon":{"paths":["M0 768h1024v128h-1024zM512 128l512 512h-1024z"],"tags":["eject","player"],"defaultCode":59941,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"eject, player17","name":"eject","order":18,"id":294,"prevSize":32,"code":59941},"setIdx":2,"setId":1,"iconIdx":293},{"icon":{"width":1088,"paths":["M890.040 922.040c-12.286 0-24.566-4.686-33.942-14.056-18.744-18.746-18.744-49.136 0-67.882 87.638-87.642 135.904-204.16 135.904-328.1 0-123.938-48.266-240.458-135.904-328.098-18.744-18.746-18.744-49.138 0-67.882s49.138-18.744 67.882 0c105.77 105.772 164.022 246.4 164.022 395.98s-58.252 290.208-164.022 395.98c-9.372 9.372-21.656 14.058-33.94 14.058zM719.53 831.53c-12.286 0-24.566-4.686-33.942-14.056-18.744-18.744-18.744-49.136 0-67.882 131.006-131.006 131.006-344.17 0-475.176-18.744-18.746-18.744-49.138 0-67.882 18.744-18.742 49.138-18.744 67.882 0 81.594 81.59 126.53 190.074 126.53 305.466 0 115.39-44.936 223.876-126.53 305.47-9.372 9.374-21.656 14.060-33.94 14.060v0zM549.020 741.020c-12.286 0-24.568-4.686-33.942-14.058-18.746-18.746-18.746-49.134 0-67.88 81.1-81.1 81.1-213.058 0-294.156-18.746-18.746-18.746-49.138 0-67.882s49.136-18.744 67.882 0c118.53 118.53 118.53 311.392 0 429.922-9.372 9.368-21.656 14.054-33.94 14.054z","M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-high","volume","audio","speaker","player"],"defaultCode":59942,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-high, volume","name":"volume-high","order":19,"id":295,"prevSize":32,"code":59942},"setIdx":2,"setId":1,"iconIdx":294},{"icon":{"paths":["M719.53 831.53c-12.286 0-24.566-4.686-33.942-14.056-18.744-18.744-18.744-49.136 0-67.882 131.006-131.006 131.006-344.17 0-475.176-18.744-18.746-18.744-49.138 0-67.882 18.744-18.742 49.138-18.744 67.882 0 81.594 81.59 126.53 190.074 126.53 305.466 0 115.39-44.936 223.876-126.53 305.47-9.372 9.374-21.656 14.060-33.94 14.060v0zM549.020 741.020c-12.286 0-24.566-4.686-33.942-14.058-18.746-18.746-18.746-49.134 0-67.88 81.1-81.1 81.1-213.058 0-294.156-18.746-18.746-18.746-49.138 0-67.882s49.136-18.744 67.882 0c118.53 118.53 118.53 311.392 0 429.922-9.372 9.368-21.656 14.054-33.94 14.054z","M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-medium","volume","audio","speaker","player"],"defaultCode":59943,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-medium, volume2","name":"volume-medium","order":20,"id":296,"prevSize":32,"code":59943},"setIdx":2,"setId":1,"iconIdx":295},{"icon":{"paths":["M549.020 741.020c-12.286 0-24.566-4.686-33.942-14.058-18.746-18.746-18.746-49.134 0-67.88 81.1-81.1 81.1-213.058 0-294.156-18.746-18.746-18.746-49.138 0-67.882s49.136-18.744 67.882 0c118.53 118.53 118.53 311.392 0 429.922-9.372 9.368-21.656 14.054-33.94 14.054z","M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-low","volume","audio","speaker","player"],"defaultCode":59944,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-low, volume3","name":"volume-low","order":21,"id":297,"prevSize":32,"code":59944},"setIdx":2,"setId":1,"iconIdx":296},{"icon":{"paths":["M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-mute","volume","audio","speaker","player"],"defaultCode":59945,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-mute, volume4","name":"volume-mute","order":22,"id":298,"prevSize":32,"code":59945},"setIdx":2,"setId":1,"iconIdx":297},{"icon":{"paths":["M960 619.148v84.852h-84.852l-107.148-107.148-107.148 107.148h-84.852v-84.852l107.148-107.148-107.148-107.148v-84.852h84.852l107.148 107.148 107.148-107.148h84.852v84.852l-107.148 107.148 107.148 107.148z","M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-mute","volume","audio","player"],"defaultCode":59946,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-mute2, volume5","name":"volume-mute2","order":23,"id":299,"prevSize":32,"code":59946},"setIdx":2,"setId":1,"iconIdx":298},{"icon":{"paths":["M1024 576h-192v192h-128v-192h-192v-128h192v-192h128v192h192v128z","M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-increase","volume","audio","speaker","player"],"defaultCode":59947,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-increase, volume6","name":"volume-increase","order":24,"id":300,"prevSize":32,"code":59947},"setIdx":2,"setId":1,"iconIdx":299},{"icon":{"paths":["M512 448h512v128h-512v-128z","M416.006 960c-8.328 0-16.512-3.25-22.634-9.374l-246.626-246.626h-114.746c-17.672 0-32-14.326-32-32v-320c0-17.672 14.328-32 32-32h114.746l246.626-246.628c9.154-9.154 22.916-11.89 34.874-6.936 11.958 4.952 19.754 16.622 19.754 29.564v832c0 12.944-7.796 24.612-19.754 29.564-3.958 1.64-8.118 2.436-12.24 2.436z"],"tags":["volume-decrease","volume","audio","speaker","player"],"defaultCode":59948,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"volume-decrease, volume7","name":"volume-decrease","order":25,"id":301,"prevSize":32,"code":59948},"setIdx":2,"setId":1,"iconIdx":300},{"icon":{"paths":["M128 320h640v192l256-256-256-256v192h-768v384h128zM896 704h-640v-192l-256 256 256 256v-192h768v-384h-128z"],"tags":["loop","repeat","player"],"defaultCode":59949,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"loop, repeat","name":"loop","order":27,"id":302,"prevSize":32,"code":59949},"setIdx":2,"setId":1,"iconIdx":301},{"icon":{"paths":["M889.68 166.32c-93.608-102.216-228.154-166.32-377.68-166.32-282.77 0-512 229.23-512 512h96c0-229.75 186.25-416 416-416 123.020 0 233.542 53.418 309.696 138.306l-149.696 149.694h352v-352l-134.32 134.32z","M928 512c0 229.75-186.25 416-416 416-123.020 0-233.542-53.418-309.694-138.306l149.694-149.694h-352v352l134.32-134.32c93.608 102.216 228.154 166.32 377.68 166.32 282.77 0 512-229.23 512-512h-96z"],"tags":["loop","repeat","player","reload","refresh","update","synchronize","arrows"],"defaultCode":59950,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"loop2, repeat2","name":"loop2","order":28,"id":303,"prevSize":32,"code":59950},"setIdx":2,"setId":1,"iconIdx":302},{"icon":{"paths":["M783.988 752.012c-64.104 0-124.372-24.96-169.7-70.288l-102.288-102.282-102.276 102.27c-45.332 45.336-105.6 70.3-169.706 70.3-64.118 0-124.39-24.964-169.722-70.3-45.332-45.334-70.296-105.604-70.296-169.712s24.964-124.38 70.296-169.714c45.334-45.332 105.608-70.296 169.714-70.296 64.108 0 124.38 24.964 169.712 70.296l102.278 102.276 102.276-102.276c45.332-45.332 105.604-70.298 169.712-70.298 64.112 0 124.384 24.966 169.71 70.298 45.338 45.334 70.302 105.606 70.302 169.714 0 64.112-24.964 124.382-70.3 169.71-45.326 45.336-105.598 70.302-169.712 70.302zM681.72 614.288c27.322 27.31 63.64 42.354 102.268 42.352 38.634 0 74.958-15.044 102.276-42.362 27.316-27.322 42.364-63.644 42.364-102.278s-15.046-74.956-42.364-102.274c-27.32-27.318-63.64-42.364-102.276-42.364-38.632 0-74.956 15.044-102.278 42.364l-102.268 102.274 102.278 102.288zM240.012 367.362c-38.634 0-74.956 15.044-102.274 42.364-27.32 27.318-42.364 63.64-42.364 102.274 0 38.632 15.044 74.954 42.364 102.276 27.32 27.316 63.642 42.364 102.274 42.364 38.634 0 74.956-15.044 102.272-42.362l102.276-102.278-102.276-102.274c-27.318-27.32-63.64-42.366-102.272-42.364v0z"],"tags":["infinite"],"defaultCode":59951,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"infinite","name":"infinite","order":29,"id":304,"prevSize":32,"code":59951},"setIdx":2,"setId":1,"iconIdx":303},{"icon":{"paths":["M768 704h-101.49l-160-160 160-160h101.49v160l224-224-224-224v160h-128c-16.974 0-33.252 6.744-45.254 18.746l-178.746 178.744-178.746-178.746c-12-12-28.28-18.744-45.254-18.744h-192v128h165.49l160 160-160 160h-165.49v128h192c16.974 0 33.252-6.742 45.254-18.746l178.746-178.744 178.746 178.744c12.002 12.004 28.28 18.746 45.254 18.746h128v160l224-224-224-224v160z"],"tags":["shuffle","random","player"],"defaultCode":59952,"grid":16,"attrs":[]},"attrs":[],"properties":{"ligatures":"shuffle, random","name":"shuffle","order":26,"id":305,"prevSize":32,"code":59952},"setIdx":2,"setId":1,"iconIdx":304}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon","majorVersion":1,"minorVersion":0},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false,"showSelector":false,"showMetrics":false,"showMetadata":false,"showVersion":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":4473924,"bgColor":16777215,"classSelector":".icon"},"historySize":100,"showCodes":true}} \ No newline at end of file diff --git a/modules/openy_digital_signage_playlist/js/playlist-control.js b/modules/openy_digital_signage_playlist/js/playlist-control.js new file mode 100644 index 0000000..63f5cca --- /dev/null +++ b/modules/openy_digital_signage_playlist/js/playlist-control.js @@ -0,0 +1,131 @@ +/** + * @file + * Provides OpenY Digital Signage playlist control related behavior. + */ + +(function ($, window, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Static bar specific behaviour. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the block. + */ + Drupal.behaviors.openyDigitalSignagePlaylistControl = { + attach: function (context, settings) { + if (context == document) { + var handler = new OpenYDigitalSignagePlaylistControl(); + handler.drawControlTray(); + window.tm.speed = 1; + } + } + }; + + /** + * Playlist controller. + * + * @returns {OpenYDigitalSignagePlaylistControl} + */ + function OpenYDigitalSignagePlaylistControl() { + var self = this; + + // General loop – swaps playlist items. + this.getEditLink = function () { + return $('.playlist-wrapper').data('edit-link'); + }; + + this.drawControlTray = function () { + let wrapperMarkup = '
' + + '
' + + ' ' + + '
' + + '
' + + '
'; + + let buttonTemplateMarkup = '
  • ' + + ' ' + + ' ' + + ' ' + + ' ' + + '
  • '; + + var buttons = [ + { icon: 'previous', title: 'Previous & play backward', link: 'javascript:;', disabled: true }, + { icon: 'backward', title: 'Backward', link: 'javascript:;', disabled: true }, + { icon: 'pause', title: 'Pause', link: 'javascript:;', disabled: true }, + { icon: 'play2', title: 'Play', link: 'javascript:;', disabled: true }, + { icon: 'forward2', title: 'Fast Forward', link: 'javascript:;', disabled: true }, + { icon: 'next', title: 'Next', link: 'javascript:;', disabled: true }, + { icon: 'edit', title: 'Edit', link: self.getEditLink(), disabled: false }, + ]; + + var $wrapper = $(wrapperMarkup).appendTo('body'); + $(buttons).each(function () { + let $button = $(buttonTemplateMarkup).appendTo($wrapper.find('.playlist-control-tabs')); + $('.playlist-control-button', $button).attr({ + title: Drupal.t(this.title), + href: this.link + }); + $('.playlist-control-icon', $button).addClass('playlist-control-icon--' + this.icon); + if (this.disabled) { + $('.playlist-control-icon', $button).addClass(' playlist-control-button--disabled'); + } + // Set button text like this: + // $('.playlist-control-tab-title', $button).text(Drupal.t(this.title)); + switch (this.icon) { + case 'previous': + $('.playlist-control-button', $button).on('click', function () { + self.setSpeed(-1); + window.tm.offset = window.OpenYDSPlaylistHandler.offset + window.OpenYDSPlaylistHandler.from - 0.01; + }); + break; + + case 'backward': + $('.playlist-control-button', $button).on('click', function () { + self.setSpeed(-10); + }); + break; + + case 'play2': + $('.playlist-control-button', $button).on('click', function () { + self.setSpeed(1); + }); + break; + + case 'pause': + $('.playlist-control-button', $button).on('click', function () { + self.setSpeed(0); + }); + break; + + case 'forward2': + $('.playlist-control-button', $button).on('click', function () { + self.setSpeed(10); + }); + break; + + case 'next': + $('.playlist-control-button', $button).on('click', function () { + self.setSpeed(1); + window.tm.offset = window.OpenYDSPlaylistHandler.offset + window.OpenYDSPlaylistHandler.from + parseInt($('.playlist-item--active').data('duration')); + }); + break; + } + }); + }; + + this.setSpeed = function (speed = 1) { + window.tm.offset = window.tm.getTime(); + window.tm.initTime = window.tm.getRealTime(); + window.tm.speed = speed; + }; + + return this; + } + +})(jQuery, window, Drupal, drupalSettings); + diff --git a/modules/openy_digital_signage_playlist/js/playlist-edit-form.js b/modules/openy_digital_signage_playlist/js/playlist-edit-form.js new file mode 100644 index 0000000..9468590 --- /dev/null +++ b/modules/openy_digital_signage_playlist/js/playlist-edit-form.js @@ -0,0 +1,79 @@ +/** + * @file + * Provides OpenY Digital Signage playlist edit form related behavior. + */ + +(function ($, window, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Playlist edit form behaviour. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the playlist markup. + */ + Drupal.behaviors.openyDigitalSignageEditPlaylist = { + attach: function (context, settings) { + var playlistItems = $('.item-wrapper .openy_ds_playlist_item', context); + var filtersElements = $('.playlist-filters .form-select', context); + + if (playlistItems.length === 0) { + return; + } + + // Add classes for parent table elements. + playlistItems.each(function() { + $(this).closest('tr.draggable').addClass('item-' + $(this).attr('data-item-status')); + }); + + // Apply filters on each behavior call with not empty playlist items. + applyPlaylistItemsFilter(playlistItems, filtersElements); + + filtersElements.change(function() { + // Apply filters on form select change. + applyPlaylistItemsFilter(playlistItems, filtersElements); + }); + } + }; + + /** + * Playlist filters behaviour. + */ + function applyPlaylistItemsFilter($playlistItems, $filtersElements) { + // Get active filters. + var filters = {}; + $filtersElements.each(function() { + var selectedValue = $(this).val(); + if (selectedValue !== 'all') { + filters[$(this).attr('data-filter-name')] = selectedValue; + } + }); + + // Show all items. + $playlistItems.each(function() { + var tableRow = $(this).closest('tr.draggable'); + tableRow.css('display', 'table-row'); + tableRow.find('a.tabledrag-handle').show(); + }); + + // Apply filters. + var filterKeys = Object.keys(filters); + if (filterKeys.length > 0) { + $playlistItems.each(function() { + var tableRow = $(this).closest('tr.draggable'); + // Disable rows draggable. + tableRow.find('a.tabledrag-handle').hide(); + for (var i = 0, len = filterKeys.length; i < len; i++) { + if ($(this).attr('data-item-' + filterKeys[i]) !== filters[filterKeys[i]]) { + // Hide inappropriate items rows. + tableRow.css('display', 'none'); + } + } + }); + } + } + +})(jQuery, window, Drupal, drupalSettings); diff --git a/modules/openy_digital_signage_playlist/js/playlist-item-edit-form.js b/modules/openy_digital_signage_playlist/js/playlist-item-edit-form.js new file mode 100644 index 0000000..e13ef4b --- /dev/null +++ b/modules/openy_digital_signage_playlist/js/playlist-item-edit-form.js @@ -0,0 +1,31 @@ +/** + * @file + * Provides OpenY Digital Signage playlist item edit form related behavior. + */ + +(function ($, window, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Playlist item edit form behaviour. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the playlist item markup. + */ + Drupal.behaviors.openyDigitalSignageEditPlaylistItem = { + attach: function (context, settings) { + // Playlist autocomplete input is displayed only when item type is 'playlist'. + // To prevent issues with autocomplete validation we should clean this field for 'media' type. + $('.openy-ds-playlist-item-form select[name=type]').once().on('change', function(e) { + e.preventDefault(); + if ($(this).val() === 'media') { + $('.form-type-entity-autocomplete input').val(''); + } + }); + } + }; + +})(jQuery, window, Drupal, drupalSettings); diff --git a/modules/openy_digital_signage_playlist/js/playlist.js b/modules/openy_digital_signage_playlist/js/playlist.js new file mode 100644 index 0000000..f9caf66 --- /dev/null +++ b/modules/openy_digital_signage_playlist/js/playlist.js @@ -0,0 +1,536 @@ +/** + * @file + * Provides OpenY Digital Signage playlist related behavior. + */ + +(function ($, window, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Playlist-specific behaviour. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the playlist markup. + */ + Drupal.behaviors.openyDigitalSignageBlockPlaylist = { + attach: function (context, settings) { + $('.block-playlist', context).once().each(function () { + var handler = new OpenYDigitalSignagePlaylist(this); + handler.deactivate(); + handler.updateContext(this); + handler.init(); + }); + } + }; + + /** + * Playlist handler. + * + * The block expects the markup where each single playlist item has + * data-duration attribute and the parent .screen-content element has + * data-from and data-to attributes. + * + * Playlist items can have optional data-rotating-from and data-rotating-to + * attributes. + * + * The math behind this block is the following: + * - the first playlist item must be shown at the moment the playlist's + * schedule item starts; + * - the next playlist item to show is the first following playlist item + * that have data-rotating-from and data-rotating-to empty or if they + * match the current time; + * - the algorithm: + * - set current playlist item index to 0 + * - set the current offset to 0 + * - loop thru the playlist items from the current playlist item index + * adding the duration to the offset + * - once the end offset of the playlist items is greater then the + * current offset the target playlist item is reached + * TODO: rewrite; + * - save its end offset for the playlist item, so that next time you + * can start the loop from that point + * + * + * @param context + * Block. + * + * @returns {OpenYDigitalSignagePlaylist} + */ + function OpenYDigitalSignagePlaylist(context) { + var self = this; + this.activated = 0; + this.animationDuration = 200; + this.context = context; + this.element = $(context); + this.current = null; + this.imagePreloadDelay = 3000; + this.index = 0; + this.loopPeriod = 1000; + this.offset = 0; + this.speed = 0; + this.time = null; + + // General loop – swaps playlist items. + this.loop = function () { + if (self.speed != window.tm.speed) { + self.reactivateLoop(); + } + if (self.isEmpty()) { + return; + } + + if (self.needsActualization()) { + self.actualize(); + } + }; + + // Checks if the current class needs replacing. + this.needsActualization = function () { + let item = this.getDesiredItem(); + if (self.current == null) { + return true; + } + return item.data('playlist-item-index') !== self.current.data('playlist-item-index'); + }; + + // Changes the current class with the upcoming. + this.actualize = function () { + var $item = this.getDesiredItem(); + + if (!$item.hasClass('playlist-item--background-set')) { + // Force item to set its background image and start downloading it, if it wasn't set yet. + $item + .css({ backgroundImage: 'url(' + this.getPlaylistItemImage($item) + ')' }) + .addClass('playlist-item--background-set'); + } + + // There was no slide active, immediately make the desirable one visible and active. + if (self.current === null) { + self.current = $item; + $item.addClass('playlist-item--active'); + return; + } + + var previous = self.current; + $item + .addClass('playlist-item--active') + .addClass('playlist-item--activating') + .css({opacity: 0}) + .animate({opacity: 1}, self.animationDuration / Math.abs(window.tm.speed), function() { + if (previous !== null) { + previous.removeClass('playlist-item--active'); + $item.removeClass('playlist-item--activating'); + } + self.current = $item; + }); + + return false; + }; + + /** + * Returns current time. + */ + this.getTimeOffset = function () { + return window.tm.getTime(); + }; + + /** + * Returns direction of playlist items. + * + * @returns {number} + * 1 for forward direction + * 0 for pause + * -1 for backward direction + */ + this.getDirection = function () { + return Math.sign(window.tm.speed); + }; + + /** + * Returns current and the upcoming classes. + * + * @returns {{last: *, next: *}} + * Object with next and last classes. + */ + this.getDesiredItem = function () { + var $items = self.getPlaylistItems(); + + // Pause. + if (!this.getDirection()) { + return $items[self.index]; + } + + var i = 0; + // In the worst case each element is 1s long, the schedule item starts at the midnight + // and it's couple of seconds before the next midnight. + while (i < 86400) { + // If there is nothing valid at the calculated intermediate offset time + // look for the first valid item and jump to it. + if (!self.isPlaylistValidAt(self.offset + self.from)) { + let nextValidPlaylistItem = self.getNextValidPlaylistItem(); + self.index = nextValidPlaylistItem.item.data('playlist-item-index'); + self.offset = nextValidPlaylistItem.offset - self.from; + } + // Non-valid playlist item met. + if (!this.isPlaylistItemValid($items[self.index], self.offset + self.from)) { + i++; + self.index = ($items.length + self.index + self.getDirection()) % $items.length; + continue; + } + let newOffset = self.offset; + if (self.getDirection() > 0) { + newOffset += self.getPlaylistItemDuration($items[self.index]); + if (newOffset >= self.getTimeOffset() - self.from) { + break; + } + } + else { + if (newOffset < self.getTimeOffset() - self.from) { + break; + } + let prevIndex = ($items.length + self.index - 1) % $items.length; + newOffset -= self.getPlaylistItemDuration($items[prevIndex]); + } + self.offset = newOffset; + self.index = ($items.length + self.index + self.getDirection()) % $items.length; + i++; + } + + return $items[self.index]; + }; + + this.getNextValidPlaylistItem = function () { + // So we are at self.offset + self.from moment of time. + let timeMoment = self.offset + self.from; + // The time direction defines where we are looking. + let timeDirection = self.getDirection(); + + let time = moment + .unix(timeMoment) + .tz(drupalSettings.digital_signage_playlist.timezone) + .format('HH:mm:ss'); + + let items = self.getPlaylistItems(); + let itemsValidForDay = []; + $(items).each(function () { + let $item = $(this); + // Ignore any playlist items that don't start today. + if (!self.isPlaylistItemValidForDay($item, timeMoment)) { + return; + } + + if (timeDirection > 0) { + // This playlist item finishes earlier, ignore it. + if (self.getPlaylistItemTimeTo($item) && self.getPlaylistItemTimeFrom($item) < time) { + return; + } + } + else { + // This playlist item finishes earlier, ignore it. + if (self.getPlaylistItemTimeTo($item) && self.getPlaylistItemTimeFrom($item) > time) { + return; + } + } + + itemsValidForDay.push($item); + }); + + // Only those that start time doesn't overlapped with other items. + let itemsThatStartSequence = []; + $(itemsValidForDay).each(function () { + let checked = this; + let startsSequence = true; + $(itemsValidForDay).each(function () { + if ( + (self.getPlaylistItemTimeFrom(this) < self.getPlaylistItemTimeFrom(checked)) && + (self.getPlaylistItemTimeTo(this) > self.getPlaylistItemTimeFrom(checked)) + ) { + startsSequence = false; + } + }); + if (startsSequence) { + itemsThatStartSequence.push(checked); + } + }); + + let closest = null; + // Now pick the item with the smallest possible start time. + $(itemsThatStartSequence).each(function () { + // This playlist item is the first found, save it as the closest. + if (!closest) { + closest = this; + return; + } + + if (timeDirection > 0) { + // There has been one playlist item that fits, check if it's closer to the point. + if (self.getPlaylistItemTimeFrom(this) >= self.getPlaylistItemTimeFrom(closest)) { + return; + } + } + else { + // There has been one playlist item that fits, check if it's closer to the point. + if (self.getPlaylistItemTimeFrom(this) <= self.getPlaylistItemTimeFrom(closest)) { + return; + } + } + + closest = this; + }); + + let closestTime = self.getPlaylistItemTimeFrom(closest).split(':'); + let offset = moment.unix(timeMoment) + .tz(drupalSettings.digital_signage_playlist.timezone) + .set('hour', parseInt(closestTime[0])) + .set('minute', parseInt(closestTime[1])) + .set('second', parseInt(closestTime[2])); + return { + item: closest, + offset: offset.unix() + }; + }; + + /** + * Checks if the playlist item is valid to be shown at the moment. + * + * @param $item + * + * @returns {boolean} + */ + this.isPlaylistItemValid = function ($item, timeMoment = null) { + if (!timeMoment) { + timeMoment = this.getTimeOffset(); + } + let datetime = moment.unix(timeMoment).tz(drupalSettings.digital_signage_playlist.timezone); + let date = datetime.format('YYYY-MM-DD'); + let time = datetime.format('HH:mm:ss'); + + if (!this.isPlaylistItemValidForDay($item, timeMoment)) { + return false; + } + if (this.getPlaylistItemTimeFrom($item) && this.getPlaylistItemTimeFrom($item) > time) { + return false; + } + if (this.getPlaylistItemTimeTo($item) && this.getPlaylistItemTimeTo($item) <= time) { + return false; + } + + return true; + }; + + /** + * Checks if the playlist item is valid on the day of the given time moment. + * + * @param $item + * + * @returns {boolean} + */ + this.isPlaylistItemValidForDay = function ($item, timeMoment = null) { + if (!timeMoment) { + timeMoment = this.getTimeOffset(); + } + let datetime = moment.unix(timeMoment).tz(drupalSettings.digital_signage_playlist.timezone); + let date = datetime.format('YYYY-MM-DD'); + + if (this.getPlaylistItemDateFrom($item) && this.getPlaylistItemDateFrom($item) > date) { + return false; + } + if (this.getPlaylistItemDateTo($item) && this.getPlaylistItemDateTo($item) < date) { + return false; + } + + return true; + }; + + this.getPlaylistItems = function () { + let $items = []; + $('.playlist-item', self.context).each(function () { + $items.push($(this)); + }); + return $items; + }; + + this.getPlaylistItemDuration = function ($el) { + return $el.data('duration'); + }; + + this.getPlaylistItemDateFrom = function ($el) { + return $el.data('date-from'); + }; + + this.getPlaylistItemDateTo = function ($el) { + return $el.data('date-to'); + }; + + this.getPlaylistItemTimeFrom = function ($el) { + return $el.data('time-from'); + }; + + this.getPlaylistItemTimeTo = function ($el) { + return $el.data('time-to'); + }; + + this.getPlaylistItemImage = function ($el) { + if ($el.data('background') != '') { + return $el.data('background'); + } + // TODO: What should be done here? + let rnd = Math.floor(Math.random() * 206); + return "https://source.unsplash.com/collection/466697/" + rnd + ""; + }; + + /** + * Checks if the playlist is empty at the moment. + * + * @returns {boolean} + */ + this.isEmpty = function () { + let $items = self.getPlaylistItems(); + + // The playlist is literally empty - there are no playlist items. + if ($items.length === 0) { + return true; + } + + return !self.isPlaylistValidAt(); + }; + + /** + * Checks if the playlist has at least one valid element at the specific moment of time. + * + * @param timeMoment + * Moment of time to be checked. + * + * @returns {boolean} + */ + this.isPlaylistValidAt = function (timeMoment = null) { + if (!timeMoment) { + timeMoment = self.getTimeOffset(); + } + + let validItemsCount = 0; + let $items = self.getPlaylistItems(); + $($items).each(function () { + if (self.isPlaylistItemValid(this, timeMoment)) { + validItemsCount++; + } + }); + + return validItemsCount > 0; + }; + + /** + * Activates the block. + */ + this.activate = function () { + var parent = $(self.context).parents('.screen-content').get(0); + + if (typeof parent == 'undefined') { + // Preview. + var query_params = window.tm.get_query_param(); + if (query_params.hasOwnProperty('from')) { + this.from = parseInt(query_params.from); + } + else { + this.from = window.tm.getTime(); + } + } + else { + this.from = $(parent).data('from-ts'); + } + + self.reactivateLoop(); + self.loop(); + self.activated = self.getTimeOffset(); + }; + + /** + * Reactivates loop. + */ + this.reactivateLoop = function () { + if (self.timer) { + clearInterval(self.timer); + } + self.timer = setInterval(self.loop, self.loopPeriod / Math.abs(window.tm.speed)); + self.speed = window.tm.speed; + }; + + /** + * Deactivates the block. + */ + this.deactivate = function () { + clearInterval(self.timer); + self.activated = 0; + }; + + /** + * Initialize block. + */ + this.init = function () { + var i = 0; + $('.playlist-item', self.context).each(function (i) { + $(this).data('playlist-item-index', i); + let img = self.getPlaylistItemImage($(this)); + var $playlistItem = $(this); + self.preloadImage(img, function() { + $playlistItem.css({ + backgroundImage: 'url(' + this + ')' + }); + }, self.imagePreloadDelay); + }); + + self.blockObject = ObjectsManager.getObject(self.context); + if (!(self.blockObject instanceof OpenYDigitalSignagePlaylist)) { + this.element.data('screenContentBlock', self); + self.activate(); + } + + // Preview. + if (self.blockObject.isActive() || $(self.context).parents('.screen').length === 0) { + self.activate(); + if ($(self.context).parents('.screen').length === 0) { + window.OpenYDSPlaylistHandler = self; + } + } + }; + + this.preloadImage = function (url, callback, delay) { + let image = new Image(); + image.onload = function () { + callback.call(url); + }; + setTimeout(function () { + image.src = url; + }, delay); + }; + + /** + * Check is block active and initialized or not. + * + * @returns {boolean} + * Status. + */ + this.isActive = function () { + return self.activated !== 0; + }; + + /** + * Update class context. + * + * @param context + * Block. + */ + this.updateContext = function (context) { + self.context = context; + self.element = $(context); + // self.element.data('screenContentBlock', self); + self = this; + }; + + return this; + } + +})(jQuery, window, Drupal, drupalSettings); diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.info.yml b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.info.yml new file mode 100644 index 0000000..12fb39b --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.info.yml @@ -0,0 +1,15 @@ +name: Open Y Digital Signage Playlists +type: module +description: Add Playlists to Open Y Digital Signage. +core: 8.x +package: "OpenY: Digital Signage" +dependencies: + - openy_digital_signage_schedule + - datetime + - duration_field + - media_entity + - openy_media_image + - entity_browser + - openy_digital_signage_schedule + - openy_digital_signage_screen + - openy_ds_media_library diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.install b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.install new file mode 100644 index 0000000..e1722c6 --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.install @@ -0,0 +1,51 @@ +getPermissions(); + + $permissions_by_provider = []; + foreach ($permissions as $permission_name => $permission) { + $permissions_by_provider[$permission['provider']][$permission_name] = $permission; + } + + // 'Digital signage admin' has full access to playlist pages. + // 'Digital signage editor' doesn't have access to playlist settings. + if (isset($permissions_by_provider['openy_digital_signage_playlist'])) { + foreach ($permissions_by_provider['openy_digital_signage_playlist'] as $permission => $data) { + $roles['digital_signage_admin']->grantPermission($permission); + + if ($permission != 'administer digital signage playlist entities' && $permission != 'administer digital signage playlist item entities') { + $roles['digital_signage_editor']->grantPermission($permission); + } + } + + $roles['digital_signage_admin']->save(); + $roles['digital_signage_editor']->save(); + } + +} + +/** + * Set default permissions for DS roles. + */ +function openy_digital_signage_playlist_update_8001(&$sandbox) { + openy_digital_signage_playlist_install(); +} diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.libraries.yml b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.libraries.yml new file mode 100644 index 0000000..97562e5 --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.libraries.yml @@ -0,0 +1,36 @@ +playlist_items_widget: + version: 1.x + css: + component: + css/playlist_items_widget.css: {} + js: + js/playlist-edit-form.js: {} + +playlist_item_form: + version: 1.x + js: + js/playlist-item-edit-form.js: {} + +openy_ds_playlist: + version: 1.x + css: + component: + css/playlist.css: {} + js: + js/playlist.js: {} + dependencies: + - core/drupal + - core/jquery + - openy_digital_signage_blocks/momentjs + - openy_digital_signage_blocks/momentjs-timezone + +openy_ds_playlist_control: + version: 1.x + css: + component: + css/playlist-control.css: {} + js: + js/playlist-control.js: {} + dependencies: + - core/drupal + - core/jquery diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.action.yml b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.action.yml new file mode 100644 index 0000000..feeb5cb --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.action.yml @@ -0,0 +1,5 @@ +entity.openy_digital_signage_playlist.add_form: + route_name: entity.openy_digital_signage_playlist.add_form + title: 'Add Playlist' + appears_on: + - view.playlists.page diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.menu.yml b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.menu.yml new file mode 100644 index 0000000..4125b00 --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.menu.yml @@ -0,0 +1,16 @@ + +# Digital Signage Playlists menu items definition +openy_digital_signage_playlist.admin.structure.settings: + title: 'Playlist settings' + description: 'Configure Digital Signage Playlist entities' + route_name: openy_digital_signage_playlist.settings + parent: system.admin_openy_digital_signage_settings + weight: -100 + +# Digital Signage Playlist Item menu items definition +openy_ds_playlist_item.admin.structure.settings: + title: 'Playlist Item settings' + description: 'Configure Digital Signage Playlist Item entities' + route_name: openy_ds_playlist_item.settings + parent: system.admin_openy_digital_signage_settings + weight: -99 diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.task.yml b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.task.yml new file mode 100644 index 0000000..7632763 --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.links.task.yml @@ -0,0 +1,43 @@ +# Digital Signage Playlist routing definition +openy_digital_signage_playlist.settings_tab: + route_name: openy_digital_signage_playlist.settings + title: 'Settings' + base_route: openy_digital_signage_playlist.settings + +entity.openy_digital_signage_playlist.canonical: + route_name: entity.openy_digital_signage_playlist.canonical + base_route: entity.openy_digital_signage_playlist.canonical + title: 'View' + +entity.openy_digital_signage_playlist.edit_form: + route_name: entity.openy_digital_signage_playlist.edit_form + base_route: entity.openy_digital_signage_playlist.canonical + title: 'Edit' + +entity.openy_digital_signage_playlist.delete_form: + route_name: entity.openy_digital_signage_playlist.delete_form + base_route: entity.openy_digital_signage_playlist.canonical + title: Delete + weight: 10 +# Digital Signage Playlist Item routing definition +openy_ds_playlist_item.settings_tab: + route_name: openy_ds_playlist_item.settings + title: 'Settings' + base_route: openy_ds_playlist_item.settings + +entity.openy_ds_playlist_item.canonical: + route_name: entity.openy_ds_playlist_item.canonical + base_route: entity.openy_ds_playlist_item.canonical + title: 'View' + +entity.openy_ds_playlist_item.edit_form: + route_name: entity.openy_ds_playlist_item.edit_form + base_route: entity.openy_ds_playlist_item.canonical + title: 'Edit' + +entity.openy_ds_playlist_item.delete_form: + route_name: entity.openy_ds_playlist_item.delete_form + base_route: entity.openy_ds_playlist_item.canonical + title: Delete + weight: 10 + diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.module b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.module new file mode 100644 index 0000000..2bc5fa6 --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.module @@ -0,0 +1,207 @@ +getBuildInfo(); + $allowed_forms = [ + 'openy_digital_signage_playlist_edit_form', + 'openy_ds_playlist_item_edit_form', + ]; + if (!in_array($info['form_id'], $allowed_forms)) { + return; + } + + if (get_class($context['widget']) === 'Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReferenceBrowserWidget') { + $class = ($info['form_id'] == 'openy_digital_signage_playlist_edit_form') ? 'playlist-eb' : 'playlist-items-eb'; + $element['#attributes']['class'][] = $class; + $element['#attached']['library'][] = 'openy_digital_signage_playlist/entity_browser'; + return; + } +} + +/** + * Implements hook_theme(). + */ +function openy_digital_signage_playlist_theme($existing, $type, $theme, $path) { + return [ + 'page__playlist' => [ + 'template' => 'page--playlist', + ], + 'openy_ds_playlist_item' => [ + 'render element' => 'elements', + ], + 'page__playlist__schedule_edit_form' => [ + 'template' => 'page--playlist--schedule-edit-form', + ], + ]; +} + +/** + * Implements hook_theme_suggestions_HOOK(). + */ +function openy_digital_signage_playlist_theme_suggestions_openy_ds_playlist_item(array $variables) { + $suggestions = []; + $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); + $suggestions[] = 'openy_ds_playlist_item__' . $sanitized_view_mode; + return $suggestions; +} + +/** + * Prepares variables for openy_ds_playlist_item templates. + * + * Default template: openy-ds-playlist-item.html.twig. + * + * @param array $variables + * An associative array containing: + * - elements: An array of elements to display in view mode. + * - entity: The openy_ds_playlist_item object. + * - view_mode: View mode; e.g., 'full', 'teaser', etc. + */ +function template_preprocess_openy_ds_playlist_item(array &$variables) { + $variables['view_mode'] = $variables['elements']['#view_mode']; + // Provide a distinct $teaser boolean. + $variables['teaser'] = $variables['view_mode'] == 'teaser'; + $entity = $variables['elements']['#openy_ds_playlist_item']; + $variables['entity'] = $entity; + // Helpful $content variable for templates. + $variables += ['content' => []]; + foreach (Element::children($variables['elements']) as $key) { + $variables['content'][$key] = $variables['elements'][$key]; + } + + $variables['attributes']['data-item-type'] = $entity->getItemType(); + $variables['attributes']['data-item-status'] = $entity->getExpireStatus(); +} + +/** + * Implements hook_theme_HOOK_alter(). + */ +function openy_digital_signage_playlist_theme_suggestions_page_alter(array &$suggestions, array $variables) { + $route_name = \Drupal::routeMatch()->getRouteName(); + if ($route_name == 'entity.openy_digital_signage_playlist.canonical') { + array_push($suggestions, 'page__playlist'); + } + if ($route_name == 'openy_digital_signage_playlist.schedule_edit_form') { + array_push($suggestions, 'page__playlist__schedule_edit_form'); + } +} + +/** + * Implements hook_preprocess_html(). + */ +function openy_digital_signage_playlist_preprocess_html(&$variables) { + $classes = ['page-schedule']; + $route_name = \Drupal::service('current_route_match')->getRouteName(); + + if (empty($variables['attributes'])) { + $variables['attributes'] = new Attribute(); + } + + if ($route_name == 'entity.openy_digital_signage_playlist.canonical') { + $variables['attributes']->addClass($classes); + $variables['page_top']['toolbar']['#access'] = FALSE; + $variables['page']['content']['tabs']['#access'] = FALSE; + } + + if ($route_name == 'openy_digital_signage_playlist.schedule_edit_form') { + $variables['page_top']['#access'] = FALSE; + $variables['page_bottom']['#access'] = FALSE; + $variables['page']['content']['tabs']['#access'] = FALSE; + } +} + +/** + * Implements hook_entity_operation(). + */ +function openy_digital_signage_playlist_entity_operation(EntityInterface $entity) { + return \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityTypeInfo::class) + ->entityOperation($entity); +} + +/** + * Implements template_preprocess_field_multiple_value_form(). + */ +function openy_digital_signage_playlist_preprocess_field_multiple_value_form(&$variables) { + if ($variables['element']['#field_name'] !== 'field_items') { + return; + } + // Add additional header pseudo columns. + $variables['table']['#header'][0]['data'] = [ + ['#markup' => t('Preview')], + ['#markup' => t('Title')], + ['#markup' => t('Type')], + ['#markup' => t('Duration')], + ['#markup' => t('Rotating date (start-end)')], + ['#markup' => t('Display time (start-end)')], + ['#markup' => t('Actions')], + ]; + + foreach (Element::children($variables['table']['#header'][0]['data']) as $key) { + // Add wrapper for columns. + $child = &$variables['table']['#header'][0]['data'][$key]; + $child['#prefix'] = ''; + $child['#suffix'] = ''; + } + + // Add wrapper for row. + $variables['table']['#header'][0]['data']['#prefix'] = '
    '; + $variables['table']['#header'][0]['data']['#suffix'] = '
    '; +} + +/** + * Implements hook_preprocess_HOOK(). + */ +function openy_digital_signage_playlist_preprocess_views_view(&$variables) { + $view = $variables['view']; + // Empty message is different for case when result is empty after filtering. + if ($view->id() == 'playlists' && !empty(array_filter($view->getExposedInput()))) { + $variables['empty'] = t('No Playlists available for display'); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function openy_digital_signage_playlist_form_entity_clone_form_alter(&$form, FormStateInterface $form_state, $form_id) { + $form['#submit'][] = '_openy_digital_signage_playlist_form_entity_clone_form_submit'; +} + +/** + * Custom submit callback for 'entity_clone_form'. + * + * Overrides entity clone form redirect. + * + * @param $form + * Nested array of form elements. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @see \Drupal\entity_clone\Form\EntityCloneForm::formSetRedirect(). + */ +function _openy_digital_signage_playlist_form_entity_clone_form_submit($form, FormStateInterface $form_state) { + // By default entity clone redirects user to 'view' page of cloned entity. + // But for playlist we should redirect user to 'edit' page instead. + $default_redirect = $form_state->getRedirect(); + + if ($default_redirect->getRouteName() == 'entity.openy_digital_signage_playlist.canonical') { + $route_params = $default_redirect->getRouteParameters(); + + if (!empty($route_params['openy_digital_signage_playlist'])) { + $form_state->setRedirect('entity.openy_digital_signage_playlist.edit_form', $route_params); + } + } +} diff --git a/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.page.inc b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.page.inc new file mode 100644 index 0000000..154ea4a --- /dev/null +++ b/modules/openy_digital_signage_playlist/openy_digital_signage_playlist.page.inc @@ -0,0 +1,30 @@ +formBuilder = $formBuilder; + $this->entityTypeManager = $entityTypeManager; + $this->dateFormatter = $dateFormatter; + } + + /** + * {@inheritdoc} + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The Drupal service container. + * + * @return static + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('form_builder'), + $container->get('entity_type.manager'), + $container->get('date.formatter') + ); + } + + /** + * Provides the playlist edit form modal dialog. + * + * @param \Drupal\openy_digital_signage_playlist\Entity\OpenYPlaylistInterface $openy_digital_signage_playlist + * Playlist entity. + * @param string $js + * Ajax|nojs. + * + * @return array|\Drupal\Core\Ajax\AjaxResponse + * Ajax Response or form array. + */ + public function edit(OpenYPlaylistInterface $openy_digital_signage_playlist, $js = 'nojs') { + $form = $this->entityFormBuilder()->getForm($openy_digital_signage_playlist, 'add'); + if ($js == 'ajax') { + $response = new AjaxResponse(); + $response->addCommand(new OpenModalDialogCommand('Edit playlist', $form, [ + 'width' => self::MODAL_WIDTH, + ])); + return $response; + } + else { + return $form; + } + } + + /** + * Provides the playlist item edit form modal dialog. + * + * @param \Drupal\openy_digital_signage_playlist\Entity\OpenYPlaylistItemInterface $openy_ds_playlist_item + * Playlist item entity. + * @param string $js + * Ajax|nojs. + * + * @return array|\Drupal\Core\Ajax\AjaxResponse + * Ajax Response or form array. + */ + public function editItem(OpenYPlaylistItemInterface $openy_ds_playlist_item, $js = 'nojs') { + $form = $this->entityFormBuilder()->getForm($openy_ds_playlist_item, 'modal'); + if ($js == 'ajax') { + $response = new AjaxResponse(); + $response->addCommand(new OpenModalDialogCommand(t('Edit playlist item'), $form, [ + 'width' => self::MODAL_WIDTH, + ])); + return $response; + } + else { + return $form; + } + } + + /** + * Provides the playlist item edit form modal dialog. + * + * @param \Drupal\openy_digital_signage_playlist\Entity\OpenYPlaylistInterface $openy_digital_signage_playlist + * Playlist entity. + * @param string $js + * Ajax|nojs. + * + * @return array|\Drupal\Core\Ajax\AjaxResponse + * Ajax Response or form array. + */ + public function addItem(OpenYPlaylistInterface $openy_digital_signage_playlist, $js = 'nojs') { + $openy_ds_playlist_item = OpenYPlaylistItem::create([]); + $form = $this->entityFormBuilder()->getForm($openy_ds_playlist_item, 'modal', [ + 'playlist' => $openy_digital_signage_playlist, + ]); + if ($js == 'ajax') { + $response = new AjaxResponse(); + $response->addCommand(new OpenModalDialogCommand(t('Add playlist item'), $form, [ + 'width' => self::MODAL_WIDTH, + ])); + return $response; + } + else { + return $form; + } + } + + /** + * Provides the playlist item remove form modal dialog. + * + * @param \Drupal\openy_digital_signage_playlist\Entity\OpenYPlaylistInterface $openy_digital_signage_playlist + * Playlist entity. + * @param \Drupal\openy_digital_signage_playlist\Entity\OpenYPlaylistItemInterface $openy_ds_playlist_item + * Playlist item entity. + * @param string $js + * Ajax|nojs. + * + * @return array|\Drupal\Core\Ajax\AjaxResponse + * Ajax Response or form array. + */ + public function removeItem(OpenYPlaylistInterface $openy_digital_signage_playlist, OpenYPlaylistItemInterface $openy_ds_playlist_item, $js = 'nojs') { + $form = $this->formBuilder->getForm('Drupal\openy_digital_signage_playlist\Form\OpenYPlaylistItemDeleteModalForm', [ + 'playlist' => $openy_digital_signage_playlist, + 'item' => $openy_ds_playlist_item, + ]); + if ($js == 'ajax') { + $response = new AjaxResponse(); + $response->addCommand(new OpenModalDialogCommand(t('Delete item'), $form, [ + 'width' => self::MODAL_WIDTH, + ])); + return $response; + } + else { + return $form; + } + } + + /** + * Provides the Assign to Screen button. + * + * @param \Drupal\openy_digital_signage_playlist\Entity\OpenYPlaylistInterface $openy_digital_signage_playlist + * Playlist entity. + * @param string $js + * Ajax|nojs. + * + * @return array|\Drupal\Core\Ajax\AjaxResponse + * Ajax Response or form array. + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + public function AddAssignScreen(OpenYPlaylistInterface $openy_digital_signage_playlist, $js = 'nojs') { + $schedule_item = $this->entityTypeManager + ->getStorage('openy_digital_signage_sch_item') + ->create([ + 'content_ref' => [ + 'target_id' => $openy_digital_signage_playlist->id(), + 'target_type' => 'openy_digital_signage_playlist', + ], + ]); + + $form = $this->entityTypeManager + ->getFormObject('openy_digital_signage_sch_item', 'assign') + ->setEntity($schedule_item); + + // The list of screens to which the current Playlist is already assigned. + $build['screen_list'] = [ + '#type' => 'table', + '#weight' => 10, + '#header' => [ + $this->t('Screen'), + $this->t('Schedule Item'), + $this->t('Time'), + $this->t('Each day'), + $this->t('Status'), + $this->t('Operation'), + ], + ]; + + $storage = $this->entityTypeManager + ->getStorage('openy_digital_signage_sch_item'); + $query = $storage + ->getQuery() + ->condition('content_ref__target_id', $openy_digital_signage_playlist->id()) + ->condition('content_ref__target_type', 'openy_digital_signage_playlist') + ->execute(); + $entities = $storage->loadMultiple($query); + + foreach ($entities as $entity) { + $time = $entity->time_slot->getValue()[0]; + $date = $entity->date->getValue()[0]; + $show_date = $entity->show_date->getString(); + $status = $entity->status->getString(); + $range_date = $date['value'] . ' - ' . $date['end_value']; + $start_timestamp = (int) strtotime($time['value'] . 'z'); + $end_timestamp = (int) strtotime($time['end_value'] . 'z'); + $start_time = $this->dateFormatter + ->format($start_timestamp, 'custom', 'h:ia'); + $end_time = $this->dateFormatter + ->format($end_timestamp, 'custom', 'h:ia'); + $storage = $this->entityTypeManager + ->getStorage('openy_digital_signage_screen'); + $query = $storage + ->getQuery() + ->condition('screen_schedule', $entity->schedule->entity->id()) + ->range(0, 1) + ->execute(); + $screen = $storage->load(array_values($query)[0]); + + $build['screen_list'][$entity->id()]['screen'] = [ + '#type' => 'link', + '#title' => $screen->label(), + '#url' => $screen->toUrl('schedule'), + ]; + $build['screen_list'][$entity->id()]['schedule_item'] = [ + '#plain_text' => $entity->label(), + ]; + $build['screen_list'][$entity->id()]['time'] = [ + '#plain_text' => $start_time . " - " . $end_time, + ]; + $build['screen_list'][$entity->id()]['each_day'] = [ + '#plain_text' => ($show_date == 0) ? $range_date : $this->t('Each day'), + ]; + $build['screen_list'][$entity->id()]['status'] = [ + '#plain_text' => ($status == 0) ? $this->t('Disabled') : $this->t('Enabled'), + ]; + $build['screen_list'][$entity->id()]['edit'] = [ + '#type' => 'container', + 'edit' => [ + '#type' => 'link', + '#title' => t('Edit'), + '#url' => Url::fromRoute('openy_ds_playlist_item.edit_schedule_item', [ + 'openy_digital_signage_sch_item' => $entity->id(), + 'js' => 'nojs', + ]), + '#attributes' => [ + 'class' => [ + 'use-ajax', + 'button', + 'field-add-more-submit', + ], + ], + ], + ]; + } + + // Wrapper for the Assign to Screen fields. + $build['assign_to_screen'] = [ + '#type' => 'details', + '#open' => TRUE, + '#weight' => 15, + '#title' => $this->t('Assign to Screen'), + ]; + $build['assign_to_screen']['elements'] = $this->formBuilder->getForm($form); + + $submit = $build['assign_to_screen']['elements']['actions']['submit']; + $submit['#value'] = $this->t('Assign'); + + $build['assign_to_screen']['elements']['submit'] = $submit; + + unset($build['assign_to_screen']['elements']['content_ref']); + unset($build['assign_to_screen']['elements']['actions']['submit']); + + if ($js == 'ajax') { + $response = new AjaxResponse(); + $response->addCommand(new OpenModalDialogCommand(t('Assign to Screen'), $build, [ + 'width' => self::MODAL_WIDTH, + ])); + return $response; + } + else { + return $build; + } + } + + public function editScheduleItem(OpenYScheduleItemInterface $openy_digital_signage_sch_item, $js = 'nojs') { + // Build an edit Schedule item form. + $form = $this->entityTypeManager() + ->getFormObject('openy_digital_signage_sch_item', 'screen') + ->setEntity($openy_digital_signage_sch_item); + $build['elements'] = $this->formBuilder->getForm($form); + $build['elements']['save'] = $build['elements']['actions']['submit']; + $build['elements']['delete'] = $build['elements']['actions']['delete']; + unset($build['elements']['actions']); + + // Return the rendered form as a proper Drupal AJAX response. + $response = new AjaxResponse(); + $response->addCommand(new HtmlCommand('.details-wrapper', $build)); + return $response; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Entity/EntityTypeInfo.php b/modules/openy_digital_signage_playlist/src/Entity/EntityTypeInfo.php new file mode 100644 index 0000000..a35e449 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Entity/EntityTypeInfo.php @@ -0,0 +1,53 @@ +getEntityTypeId() == 'openy_digital_signage_playlist') { + $operations['view'] = [ + 'title' => $this->t('View'), + 'weight' => -100, + 'url' => Url::fromRoute('entity.openy_digital_signage_playlist.canonical', [ + 'openy_digital_signage_playlist' => $entity->id(), + ]), + ]; + $operations['clone'] = [ + 'title' => $this->t('Clone'), + 'weight' => 100, + 'url' => Url::fromRoute('entity.openy_digital_signage_playlist.clone_form', [ + 'openy_digital_signage_playlist' => $entity->id(), + ]), + ]; + } + + return $operations; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylist.php b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylist.php new file mode 100644 index 0000000..71c5c32 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylist.php @@ -0,0 +1,201 @@ + \Drupal::currentUser()->id(), + ]; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->get('name')->value; + } + + /** + * {@inheritdoc} + */ + public function setName($name) { + $this->set('name', $name); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->get('created')->value; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($timestamp) { + $this->set('created', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->get('uid')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('uid')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('uid', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('uid', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isPublished() { + return (bool) $this->getEntityKey('status'); + } + + /** + * {@inheritdoc} + */ + public function setPublished($published) { + $this->set('status', $published ? TRUE : FALSE); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['uid'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Authored by')) + ->setDescription(t('The username of the content author.')) + ->setRevisionable(TRUE) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + ->setTranslatable(TRUE) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel(t('Playlist Name')) + ->setDescription(t("Enter Playlist name, it's required for internal usage to easily find needed playlist.")) + ->setSettings([ + 'max_length' => 50, + 'text_processing' => 0, + ]) + ->setDefaultValue('') + ->setDisplayOptions('view', [ + 'label' => 'above', + 'type' => 'string', + 'weight' => -4, + ]) + ->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => -4, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setRequired(TRUE); + + $fields['status'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Published')) + ->setDescription(t('Unpublished playlists can not be assigned to screens but still can be edited.')) + ->setSettings(['on_label' => 'Published', 'off_label' => 'Unpublished']) + ->setDefaultValue(TRUE) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayOptions('form', [ + 'type' => 'boolean_checkbox', + 'weight' => -3, + ]); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the entity was created.')); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setDescription(t('The time that the entity was last edited.')); + + return $fields; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistInterface.php b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistInterface.php new file mode 100644 index 0000000..84d1a03 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistInterface.php @@ -0,0 +1,76 @@ + \Drupal::currentUser()->id(), + ]; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return $this->get('name')->value; + } + + /** + * {@inheritdoc} + */ + public function setName($name) { + $this->set('name', $name); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCreatedTime() { + return $this->get('created')->value; + } + + /** + * {@inheritdoc} + */ + public function setCreatedTime($timestamp) { + $this->set('created', $timestamp); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOwner() { + return $this->get('uid')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('uid')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('uid', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('uid', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isPublished() { + return (bool) $this->getEntityKey('status'); + } + + /** + * {@inheritdoc} + */ + public function setPublished($published) { + $this->set('status', $published ? TRUE : FALSE); + return $this; + } + + /** + * Get field type value. + */ + public function getItemType() { + return $this->get('type')->value; + } + + /** + * Get expire status. + */ + public function getExpireStatus() { + $date_end = $this->get('date_end')->date; + $time_end = $this->get('time_end')->date; + $default_timezone = drupal_get_user_timezone(); + $current_date = new DrupalDateTime('now', $default_timezone); + + if (!$date_end) { + // Return STATUS_ACTIVE if end date not set. + return self::STATUS_ACTIVE; + } + + $date_end->setTimezone(new \DateTimeZone($default_timezone)); + if (!$time_end) { + // Set time to end of the day. + $date_end->setTime(23, 59); + } + else { + $time_parts = explode(':', $time_end->format('H:i:s')); + // 0 - hours, 1 - day, 2 - seconds. + $date_end->setTime($time_parts[0], $time_parts[1], $time_parts[2]); + } + + $interval = $current_date->diff($date_end); + if ($interval->invert === 1) { + return self::STATUS_EXPIRED; + } + + if ($interval->days <= 2) { + return self::STATUS_EXPIRES_SOON; + } + + return self::STATUS_ACTIVE; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel(t('Title')) + ->setSettings([ + 'max_length' => 50, + 'text_processing' => 0, + ]) + ->setDefaultValue('') + ->setDisplayOptions('view', [ + 'label' => 'above', + 'type' => 'string', + 'weight' => 0, + ]) + ->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => 0, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + //->setRequired(TRUE); + + $fields['type'] = BaseFieldDefinition::create('list_string') + ->setLabel(t('Type')) + ->setDefaultValue('media') + ->setRequired(TRUE) + ->setSettings([ + 'allowed_values' => [ + 'media' => 'Media', + 'playlist' => 'Playlist', + ], + ]) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'list_default', + 'weight' => 1, + ]) + ->setDisplayOptions('form', [ + 'type' => 'options_select', + 'weight' => 1, + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['media'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Media asset')) + // TODO: this place should refactored to use core media. + ->setSetting('target_type', 'media') + ->setSetting('handler', 'default:media') + ->setSetting('handler_settings', [ + // If we need more media bundles - add them to target_bundles. + 'target_bundles' => [ + 'image' => 'image', + ], + 'auto_create' => FALSE, + ]) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'entity_reference_entity_view', + 'weight' => 2, + 'settings' => [ + 'view_mode' => 'full_without_blazy', + 'link' => FALSE, + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'entity_browser_entity_reference', + 'weight' => 2, + 'settings' => [ + 'entity_browser' => 'digital_signage_images_library', + 'field_widget_display' => 'rendered_entity', + 'field_widget_edit' => TRUE, + 'field_widget_remove' => TRUE, + 'open' => FALSE, + 'selection_mode' => 'selection_append', + 'field_widget_display_settings' => [ + 'view_mode' => 'playlist_item_teaser', + ], + ], + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['playlist'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Playlist')) + ->setDescription(t('Here you can reference an existing playlist entity.')) + ->setSetting('target_type', 'openy_digital_signage_playlist') + ->setSetting('handler', 'default:openy_digital_signage_playlist') + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'entity_reference_entity_view', + 'weight' => 2, + 'settings' => [ + 'view_mode' => 'default', + 'link' => FALSE, + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'entity_reference_autocomplete', + 'weight' => 2, + 'settings' => [ + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'placeholder' => '', + ], + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['duration'] = BaseFieldDefinition::create('duration') + ->setLabel(t('Duration')) + ->setSettings([ + 'granularity' => [ + 'year' => FALSE, + 'month' => FALSE, + 'day' => FALSE, + 'hour' => TRUE, + 'minute' => TRUE, + 'second' => TRUE, + ], + ]) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'duration_time_display', + 'weight' => 3, + ]) + ->setDisplayOptions('form', [ + 'type' => 'duration_widget', + 'weight' => 3, + ]) + ->setRequired(FALSE) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['date_start'] = BaseFieldDefinition::create('datetime') + ->setLabel(t('Date start')) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'datetime_default', + 'weight' => 4, + 'settings' => [ + 'format_type' => 'html_date', + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'datetime_default', + 'weight' => 4, + ]) + ->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATE) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['date_end'] = BaseFieldDefinition::create('datetime') + ->setLabel(t('Date end')) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'datetime_default', + 'weight' => 5, + 'settings' => [ + 'format_type' => 'html_date', + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'datetime_default', + 'weight' => 5, + ]) + ->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATE) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['time_start'] = BaseFieldDefinition::create('datetime') + ->setLabel(t('Time start')) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'datetime_default', + 'weight' => 6, + 'settings' => [ + 'format_type' => 'html_time', + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'datetime_time_only', + 'weight' => 6, + ]) + ->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATETIME) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['time_end'] = BaseFieldDefinition::create('datetime') + ->setLabel(t('Time end')) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'datetime_default', + 'weight' => 7, + 'settings' => [ + 'format_type' => 'html_time', + ], + ]) + ->setDisplayOptions('form', [ + 'type' => 'datetime_time_only', + 'weight' => 7, + ]) + ->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATETIME) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $fields['uid'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Authored by')) + ->setRevisionable(TRUE) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['status'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Publishing status')) + ->setDescription(t('A boolean indicating whether the Digital Signage Playlist Item is published.')) + ->setSettings(['on_label' => 'Published', 'off_label' => 'Unpublished']) + ->setDefaultValue(TRUE); + + $fields['created'] = BaseFieldDefinition::create('created') + ->setLabel(t('Created')) + ->setDescription(t('The time that the entity was created.')); + + $fields['changed'] = BaseFieldDefinition::create('changed') + ->setLabel(t('Changed')) + ->setDescription(t('The time that the entity was last edited.')); + + return $fields; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistItemInterface.php b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistItemInterface.php new file mode 100644 index 0000000..735974d --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistItemInterface.php @@ -0,0 +1,76 @@ +invokeAll('ds_version'); + $version = md5(json_encode($versions)); + + $items = $this->getPlaylistItemsRecursive($entity); + + $settings = [ + 'digital_signage_playlist' => [ + 'timezone' => drupal_get_user_timezone(), + ], + ]; + + $build = [ + 'wrapper' => [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'class' => 'playlist-wrapper', + 'data-playlist-id' => $entity->id(), + 'data-app-version' => $version, + ], + 'block' => [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'class' => 'block block-playlist', + ], + 'items' => $items, + ], + ], + '#cache' => [ + 'max-age' => 0, + ], + '#attached' => [ + 'library' => [ + 'openy_digital_signage_screen/openy_ds_screen_handler', + 'openy_digital_signage_screen/openy_ds_screen_theme', + 'openy_digital_signage_playlist/openy_ds_playlist', + ], + 'drupalSettings' => $settings, + ], + ]; + + $route_name = \Drupal::routeMatch()->getRouteName(); + if ($route_name == 'entity.openy_digital_signage_playlist.canonical') { + $build['#attached']['library'][] = 'openy_digital_signage_playlist/openy_ds_playlist_control'; + $params = [ 'openy_digital_signage_playlist' => $entity->id() ]; + $options = [ + 'absolute' => TRUE, + 'query' => [ + 'destination' => \Drupal::request()->getRequestUri(), + ], + ]; + $url = Url::fromRoute('openy_digital_signage_playlist.schedule_edit_form', $params, $options); + $build['wrapper']['#attributes']['data-edit-link'] = $url->toString(); + } + + return $build; + } + + /** + * Builds playlist item array for the given playlist entity. + * + * @param \Drupal\Core\Entity\EntityInterface $playlist + * The playlist entity. + * @param array $rendered + * The array of already built playlists. Used in order to prevent loops. + * + * @return array + * Plyalist items build array. + * + * @throws \Exception + */ + public static function getPlaylistItemsRecursive(EntityInterface $playlist, array $rendered = []) { + $items = []; + foreach ($playlist->field_items->referencedEntities() as $id => $playlist_item) { + $url = ''; + // Media playlist item. + if ($playlist_item->type->value == 'media') { + if ($media = $playlist_item->media->entity) { + if ($image = $media->field_media_image->entity) { + $url = file_create_url($image->uri->value); + } + } + + $duration = new \DateInterval($playlist_item->duration->value); + $duration_seconds = $duration->s + $duration->i * 60 + $duration->h * 3600; + + $date_start = $playlist_item->date_start->isEmpty() ? '' : $playlist_item->date_start->value; + $date_end = $playlist_item->date_end->isEmpty() ? '' : $playlist_item->date_end->value; + + $time_start = ''; + if (!$playlist_item->time_start->isEmpty()) { + $date_time = DrupalDateTime::createFromFormat(DATETIME_DATETIME_STORAGE_FORMAT, $playlist_item->time_start->value, 'UTC'); + $date_time->setTimezone(timezone_open(drupal_get_user_timezone())); + $time_start = $date_time->format('H:i:s'); + } + + $time_end = ''; + $playlist_item->time_end->value; + if (!$playlist_item->time_end->isEmpty()) { + $date_time = DrupalDateTime::createFromFormat(DATETIME_DATETIME_STORAGE_FORMAT, $playlist_item->time_end->value, 'UTC'); + $date_time->setTimezone(timezone_open(drupal_get_user_timezone())); + $time_end = $date_time->format('H:i:s'); + } + + $items[] = [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'class' => 'playlist-item', + 'data-duration' => $duration_seconds, + 'data-background' => $url, + 'data-date-from' => $date_start, + 'data-date-to' => $date_end, + 'data-time-from' => $time_start, + 'data-time-to' => $time_end, + ], + ]; + } + else { + // Nested playlist. + $nested_playlist = $playlist_item->playlist->entity; + // The nested playlist is disabled. + if ($nested_playlist->status->value == 0) { + continue; + } + if (in_array($nested_playlist, $rendered)) { + continue; + } + $_rendered = array_merge($rendered, [$playlist]); + foreach ($_items = static::getPlaylistItemsRecursive($nested_playlist, $_rendered) as $_playlist_item) { + $items[] = $_playlist_item; + } + } + } + + return $items; + } + + /** + * {@inheritdoc} + */ + public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) { + $build = []; + foreach ($entities as $key => $entity) { + $build[$key] = $this->view($entity, $view_mode, $langcode); + } + return $build; + } + + /** + * {@inheritdoc} + */ + public function resetCache(array $entities = NULL) { + // Intentionally empty. + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + // Intentionally empty. + } + + /** + * {@inheritdoc} + */ + public function buildComponents(array &$build, array $entities, array $displays, $view_mode) { + throw new \LogicException(); + } + + /** + * {@inheritdoc} + */ + public function viewField(FieldItemListInterface $items, $display_options = array()) { + throw new \LogicException(); + } + + /** + * {@inheritdoc} + */ + public function viewFieldItem(FieldItemInterface $item, $display_options = array()) { + throw new \LogicException(); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistViewsData.php b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistViewsData.php new file mode 100644 index 0000000..5fb2417 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Entity/OpenYPlaylistViewsData.php @@ -0,0 +1,33 @@ + 'id', + 'title' => $this->t('Digital Signage Playlist'), + 'help' => $this->t('The Digital Signage Playlist ID.'), + ]; + + $data['openy_ds_playlist_item']['table']['base'] = [ + 'field' => 'id', + 'title' => $this->t('Digital Signage Playlist Item'), + 'help' => $this->t('The Digital Signage Playlist Item ID.'), + ]; + + return $data; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistAssignScreenForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistAssignScreenForm.php new file mode 100644 index 0000000..c5b3387 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistAssignScreenForm.php @@ -0,0 +1,253 @@ +database = $database; + $this->renderer = $renderer; + } + + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager'), + $container->get('entity_type.bundle.info'), + $container->get('datetime.time'), + $container->get('database'), + $container->get('renderer') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + /* @var $entity \Drupal\openy_digital_signage_schedule\Entity\OpenYScheduleItem */ + $form = parent::buildForm($form, $form_state); + $form['screen'] = [ + '#title' => $this->t('Select location'), + '#type' => 'select', + '#options' => $this->getScreenList(), + '#description' => $this->t('Choose Screen to assign current Playlist to'), + ]; + $form['title']['widget'][0]['value']['#description'] = $this->t('Schedule Item title'); + $form['title']['widget'][0]['value']['#prefix'] = '
    '; + unset($form['schedule']); + $form['actions']['submit']['#ajax'] = [ + 'callback' => '::ajaxSubmitCallback', + 'event' => 'click', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + $isUsed = FALSE; + $start_time = $form_state->getValue(['time_slot', 0, 'value']); + $end_time = $form_state->getValue(['time_slot', 0, 'end_value']); + $show_date = $form_state->getValue(['show_date', 'value']); + $screen_id = $form_state->getValue('screen'); + $start_date = $form_state->getValue(['date', 0, 'value']); + $end_date = $form_state->getValue(['date', 0, 'end_value']); + + // Since only the time is important and the dates are different, + // we must bring the date to a common denominator. + $duration = $end_time->getTimestamp() - $start_time->getTimestamp(); + $wd_start_time = $start_time->format('1970-01-01\TH:i:s'); + $wd_end_time = date('Y-m-d\TH:i:s', strtotime($wd_start_time) + $duration); + + $screen = $this->entityTypeManager + ->getStorage('openy_digital_signage_screen') + ->load($screen_id); + $schedule_id = $screen->screen_schedule->entity->id(); + $form_state->setValue('screen_name', $screen->label()); + $query = $this->database->select('openy_digital_signage_sch_item', 'sch'); + $query->fields('sch', [ + 'id', + 'time_slot__value', + 'time_slot__end_value', + 'show_date', + 'date__value', + 'date__end_value', + ]); + $query->condition('schedule', $schedule_id); + $times_slot = $query->execute()->fetchAllAssoc('id'); + + // We need to compare the interval of all dates and times to avoid + // overlapping schedules. + // And if this happens, then show the error message. + foreach ($times_slot as $time_slot) { + $wd_start_time_slot = date( '1970-01-01\TH:i:s', strtotime($time_slot->time_slot__value)); + $duration_sch = strtotime($time_slot->time_slot__end_value) - strtotime($time_slot->time_slot__value); + $wd_end_time_slot = date('Y-m-d\TH:i:s', strtotime($wd_start_time_slot) + $duration_sch); + + // In the first step, we check the intersection of the selected time + // interval with the interval of the current Schedule item, and then + // conversely for determine all intersections of time intervals. + if ($wd_start_time >= $wd_start_time_slot && $wd_start_time <= $wd_end_time_slot || + $wd_end_time >= $wd_start_time_slot && $wd_end_time <= $wd_end_time_slot || + $wd_start_time_slot >= $wd_start_time && $wd_start_time_slot <= $wd_end_time || + $wd_end_time_slot >= $wd_start_time && $wd_end_time_slot <= $wd_end_time) { + + // If dates are specified in the selected and current Schedule item, + // they should also be checked for intersection. + if ($time_slot->show_date == 0 && $show_date == 0) { + $start_date_slot = $time_slot->date__value; + $end_date_slot = $time_slot->date__end_value; + + if ($start_date >= $start_date_slot && $start_date <= $end_date_slot || + $end_date >= $start_date_slot && $end_date <= $end_date_slot || + $start_date_slot >= $start_date && $start_date_slot <= $end_date || + $end_date_slot >= $start_date && $end_date_slot <= $end_date) { + + $isUsed = TRUE; + break; + } + } else { + $isUsed = TRUE; + break; + } + } + } + + if ($isUsed) { + drupal_set_message( + $this->t('Time was already booked'), + 'error' + ); + $form_state->setErrorByName('time_slot'); + } + + parent::validateForm($form, $form_state); + } + + /** + * For ajax validation. + * + * @param array $form + * @param \Drupal\Core\Form\FormStateInterface $form_state + * + * @return \Drupal\Core\Ajax\AjaxResponse + * @throws \Exception + */ + public function ajaxSubmitCallback(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + + if ($form_state->getErrors()) { + $message = [ + '#theme' => 'status_messages', + '#message_list' => drupal_get_messages(), + '#status_headings' => [ + 'status' => $this->t('Status message'), + 'error' => $this->t('Error message'), + 'warning' => $this->t('Warning message'), + ], + ]; + $messages = $this->renderer->render($message); + $response->addCommand(new HtmlCommand('.validate--message', $messages)); + return $response; + } else { + $entity = $this->entity; + $redirect_url = Url::fromRoute('entity.openy_digital_signage_playlist.edit_form', [ + 'openy_digital_signage_playlist' => $entity->content_ref->entity->id(), + ])->toString(); + drupal_set_message( + $this->t('Playlist @playlist has been assigned to the screen @screen', [ + '@playlist' => $entity->content_ref->entity->label(), + '@screen' => $form_state->getValue('screen_name'), + ]), + 'status' + ); + + $command = new RedirectCommand($redirect_url); + return $response->addCommand($command); + } + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $screen_id = $form_state->getValue('screen'); + $playlist_id = $form_state->getValue('content_ref'); + + $screen = $this->entityTypeManager + ->getStorage('openy_digital_signage_screen') + ->load($screen_id); + $schedule_id = $screen->screen_schedule->entity->id(); + + $entity = $this->entity; + $entity->schedule = $schedule_id; + + parent::save($form, $form_state); + + $redirect_url = Url::fromRoute('entity.openy_digital_signage_playlist.edit_form', [ + 'openy_digital_signage_playlist' => $playlist_id[0]['target_id'], + ]); + $form_state->setRedirectUrl($redirect_url); + } + + /** + * Get the Screen entities. + * + * @return $options + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + protected function getScreenList() { + $options = []; + $query = $this->entityTypeManager + ->getStorage('openy_digital_signage_screen') + ->getQuery() + ->sort('title', 'ASC') + ->execute(); + + $screens = $this->entityTypeManager + ->getStorage('openy_digital_signage_screen') + ->loadMultiple($query); + + foreach ($screens as $screen) { + $options[$screen->id()] = $screen->label(); + } + + return $options; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistDeleteForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistDeleteForm.php new file mode 100644 index 0000000..d451d6f --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistDeleteForm.php @@ -0,0 +1,32 @@ +getEntity()->urlInfo('canonical'); + } + + /** + * Returns the URL where the user should be redirected after deletion. + * + * @return \Drupal\Core\Url + * The redirect URL. + */ + protected function getRedirectUrl() { + return Url::fromRoute('view.playlists.page'); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistEditForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistEditForm.php new file mode 100644 index 0000000..d688a35 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistEditForm.php @@ -0,0 +1,103 @@ +entity; + + $form['details'] = [ + '#type' => 'container', + '#weight' => -900, + '#attributes' => [ + 'class' => ['playlist-details', 'container-inline'], + ], + 'name' => [ + '#type' => 'page_title', + '#title' => $entity->getName(), + ], + 'actions' => [ + '#type' => 'container', + '#attributes' => ['class' => ['playlist-actions']], + 'edit_details' => [ + '#type' => 'link', + '#title' => t('Edit'), + '#url' => Url::fromRoute('openy_digital_signage_playlist.details_edit', [ + 'openy_digital_signage_playlist' => $entity->id(), + 'js' => 'nojs', + ], + [ + 'query' => [ + 'destination' => Url::fromRoute('', [], [ + 'query' => \Drupal::destination()->getAsArray(), + ])->toString(), + ], + ]), + '#attributes' => [ + 'class' => ['use-ajax', 'button'], + ], + ], + 'add_items' => $form['field_items']['widget']['add_more']['add_more'], + 'assign_to_screen' => $form['field_items']['widget']['assign_to_screen']['assign_to_screen'], + ], + ]; + + $form['filters'] = [ + '#type' => 'container', + '#weight' => -100, + '#attributes' => ['class' => ['playlist-filters', 'container-inline']], + 'filter_type' => [ + '#type' => 'select', + '#title' => $this->t('Type'), + '#attributes' => ['data-filter-name' => 'type'], + '#options' => [ + 'all' => $this->t('- Any -'), + 'media' => $this->t('Media'), + 'playlist' => $this->t('Playlist'), + ], + ], + 'filter_status' => [ + '#type' => 'select', + '#title' => $this->t('Status'), + '#attributes' => ['data-filter-name' => 'status'], + '#options' => [ + 'all' => $this->t('- Any -'), + 'active' => $this->t('Active'), + 'expires_soon' => $this->t('Expires Soon'), + 'expired' => $this->t('Expired'), + ], + ], + ]; + + unset($form['actions']['delete']); + unset($form['field_items']['widget']['add_more']['add_more']); + unset($form['field_items']['widget']['assign_to_screen']); + $form['#attached']['library'][] = 'openy_digital_signage_playlist/playlist_items_widget'; + $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + parent::save($form, $form_state); +// $form_state->setRedirect('view.playlists.page'); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistEditScheduleItemForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistEditScheduleItemForm.php new file mode 100644 index 0000000..ffc3101 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistEditScheduleItemForm.php @@ -0,0 +1,49 @@ +entity; + $storage = $this->entityTypeManager + ->getStorage('openy_digital_signage_screen'); + $query = $storage + ->getQuery() + ->condition('screen_schedule', $entity->schedule->entity->id()) + ->range(0, 1) + ->execute(); + $screen = $storage->load(array_values($query)[0]); + parent::save($form, $form_state); + + // Since we inherit the form for assign to screen it would be better to + // rewrite the text of the message + $messages = drupal_get_messages('status'); + foreach ($messages['status'] as $key => $value) { + if(strpos($value, 'Digital Signage Schedule Item') !== FALSE) { + $messages['status'][$key] = $this->t('Playlist @playlist has been assigned to the screen @screen', [ + '@playlist' => $entity->content_ref->entity->label(), + '@screen' => $screen->label(), + ]); + }; + drupal_set_message($messages['status'][$key]); + } + $redirect_url = Url::fromRoute('entity.openy_digital_signage_playlist.edit_form', [ + 'openy_digital_signage_playlist' => $entity->content_ref->entity->id(), + ]); + $form_state->setRedirectUrl($redirect_url); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistForm.php new file mode 100644 index 0000000..e4c1ce5 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistForm.php @@ -0,0 +1,113 @@ +renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager'), + $container->get('entity_type.bundle.info'), + $container->get('datetime.time'), + $container->get('renderer') + ); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + $form['status']['#suffix'] = '
    '; + $form['status']['widget']['value']['#ajax'] = [ + 'callback' => [$this, 'validateStatus'], + 'event' => 'change', + ]; + unset($form['actions']['delete']); + + return $form; + } + + /** + * Validate status. + * + * When a user wants to Unpublish a Playlist that is currently assigned + * to a screen and is active, there should be a modal window notifying + * the user that Playlist is currently active and it would stop working. + */ + public function validateStatus(array $form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + $messages = ''; + + if ($form_state->getValue('status')['value'] == 0) { + $query = $this->entityTypeManager + ->getStorage('openy_digital_signage_sch_item') + ->getQuery() + ->condition('content_ref__target_id', $this->entity->id()) + ->condition('status', 1) + ->count() + ->execute(); + + if ($query != 0) { + drupal_set_message( + $this->t('The playlist is currently in use and will stop being displayed after unpublishing. Are you sure you want to unpublish this playlist?'), + 'warning' + ); + $message = [ + '#theme' => 'status_messages', + '#message_list' => drupal_get_messages(), + '#status_headings' => [ + 'status' => $this->t('Status message'), + 'error' => $this->t('Error message'), + 'warning' => $this->t('Warning message'), + ], + ]; + $messages = $this->renderer->render($message); + } + } + $response->addCommand(new HtmlCommand('#warning-message', $messages)); + return $response; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $entity = $this->entity; + parent::save($form, $form_state); + $destination = \Drupal::request()->query->get('destination'); + if (!$destination) { + $form_state->setRedirect('entity.openy_digital_signage_playlist.edit_form', ['openy_digital_signage_playlist' => $entity->id()]); + } + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemDeleteForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemDeleteForm.php new file mode 100644 index 0000000..6030874 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemDeleteForm.php @@ -0,0 +1,33 @@ +getEntity()->urlInfo('canonical'); + } + + /** + * Returns the URL where the user should be redirected after deletion. + * + * @return \Drupal\Core\Url + * The redirect URL. + */ + protected function getRedirectUrl() { + // TODO: replace this by playlist-items list. + return Url::fromRoute('view.playlists.page'); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemDeleteModalForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemDeleteModalForm.php new file mode 100644 index 0000000..01773fa --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemDeleteModalForm.php @@ -0,0 +1,96 @@ +getBuildInfo()['args'][0]; + $message = $this->t('Are you sure you want to remove "@item" item from "@playlist" playlist ?', [ + '@item' => $build_info['item']->getName(), + '@playlist' => $build_info['playlist']->getName(), + ]); + $form['message']['#markup'] = $message; + + $form['actions'] = ['#type' => 'actions']; + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Delete'), + '#weight' => -10, + '#attributes' => ['class' => ['button--primary']], + '#ajax' => [ + 'callback' => [$this, 'originFormRefresh'], + 'progress' => ['type' => 'fullscreen'], + ], + ]; + $form['actions']['cancel'] = [ + '#weight' => 10, + '#value' => $this->t('Cancel'), + '#name' => 'cancel', + '#type' => 'button', + '#limit_validation_errors' => [], + '#ajax' => [ + 'callback' => [$this, 'closeModal'], + 'progress' => ['type' => 'fullscreen'], + ], + ]; + $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; + return $form; + } + + /** + * Ajax callback for cancel button. + */ + public static function closeModal(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + $response->addCommand(new CloseModalDialogCommand()); + return $response; + } + + /** + * Ajax callback for submit button. + */ + public static function originFormRefresh(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + $response->addCommand(new CloseModalDialogCommand()); + $response->addCommand(new InvokeCommand('.button-playlist_items-refresh', 'mousedown')); + return $response; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $build_info = $form_state->getBuildInfo()['args'][0]; + $item = $build_info['item']; + $playlist = $build_info['playlist']; + foreach ($playlist->field_items as $delta => $value) { + if ($value->target_id == $item->id()) { + $playlist->field_items->removeItem($delta); + } + } + $playlist->save(); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemForm.php new file mode 100644 index 0000000..74c62f4 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemForm.php @@ -0,0 +1,179 @@ + [ + 'select[name="type"]' => ['value' => 'media'], + ], + ]; + $form['playlist']['#states'] = [ + 'visible' => [ + 'select[name="type"]' => ['value' => 'playlist'], + ], + ]; + $form['duration']['#states'] = [ + 'visible' => [ + 'select[name="type"]' => ['value' => 'media'], + ], + 'required' => [ + 'select[name="type"]' => ['value' => 'media'], + ], + ]; + $form['duration']['widget']['#states'] = [ + 'required' => [ + 'select[name="type"]' => ['value' => 'media'], + ], + ]; + $form['duration']['widget'][0]['#states'] = [ + 'required' => [ + 'select[name="type"]' => ['value' => 'media'], + ], + ]; + $form['duration_note'] = [ + '#type' => 'container', + '#states' =>[ + 'visible' => [ + 'select[name="type"]' => ['value' => 'playlist'], + ], + ], + 'note' => [ + '#type' => 'html_tag', + '#tag' => 'p', + '#value' => $this->t('Dictated by the nested playlist duration'), + ], + ]; + + $form['date_note'] = [ + '#type' => 'container', + '#states' =>[ + 'visible' => [ + 'select[name="type"]' => ['value' => 'playlist'], + ], + ], + 'note' => [ + '#type' => 'html_tag', + '#tag' => 'p', + '#value' => $this->t('Dates are pulled from the nested playlist. Input values to additionally narrow the period down'), + ], + ]; + $form['time_note'] = [ + '#type' => 'container', + '#states' =>[ + 'visible' => [ + 'select[name="type"]' => ['value' => 'playlist'], + ], + ], + 'note' => [ + '#type' => 'html_tag', + '#tag' => 'p', + '#value' => $this->t('Time is pulled from the nested playlist. Input values to additionally narrow the period down'), + ], + ]; + + $form['#attached']['library'][] = 'openy_digital_signage_playlist/playlist_item_form'; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function processForm($element, FormStateInterface $form_state, $form) { + $processed_form = parent::processForm($element, $form_state, $form); + if (!in_array('duration_note', $processed_form['#fieldgroups']['group_duration']->children)) { + $processed_form['#fieldgroups']['group_duration']->children[] = 'duration_note'; + } + $processed_form['#group_children']['duration_note'] = 'group_duration'; + + if (!in_array('date_note', $processed_form['#fieldgroups']['group_rotating_date']->children)) { + $processed_form['#fieldgroups']['group_rotating_date']->children[] = 'date_note'; + } + $processed_form['#group_children']['date_note'] = 'group_rotating_date'; + + if (!in_array('time_note', $processed_form['#fieldgroups']['group_display_time']->children)) { + $processed_form['#fieldgroups']['group_display_time']->children[] = 'time_note'; + } + $processed_form['#group_children']['time_note'] = 'group_display_time'; + + return $processed_form; + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) { + parent::validateForm($form, $form_state); + + $date_start = $form_state->getValue(['date_start', 0, 'value']); + $date_end = $form_state->getValue(['date_end', 0, 'value']); + $time_start = $form_state->getValue(['time_start', 0, 'value']); + $time_end = $form_state->getValue(['time_end', 0, 'value']); + $type = $form_state->getValue(['type', 0, 'value']); + $playlist = $form_state->getValue(['playlist', 0, 'target_id']); + $media = $form_state->getValue(['media', 'target_id']); + $duration = $form_state->getValue(['duration', 0, 'value']); + + if ($type == 'playlist' && !$playlist) { + $form_state->setErrorByName('playlist', $this->t('Playlist field is required for selected type.')); + } + + if ($type == 'media' && !$media) { + $form_state->setErrorByName('playlist', $this->t('Media field is required for selected type.')); + } + + if ($type == 'media' && !$duration) { + $form_state->setErrorByName('duration', $this->t('Duration field is required for the selected type.')); + } + + if (!$this->validEndDate($date_start, $date_end)) { + $form_state->setErrorByName('date_end', $this->t('The end date cannot be before the start date')); + } + + if (!$this->validEndDate($time_start, $time_end)) { + $form_state->setErrorByName('time_end', $this->t('The end time cannot be before the start time')); + } + } + + /** + * Check that end date is after start date. + */ + public function validEndDate($start_date, $end_date) { + if ($start_date instanceof DrupalDateTime && $end_date instanceof DrupalDateTime) { + if ($start_date->getTimestamp() !== $end_date->getTimestamp()) { + $interval = $start_date->diff($end_date); + if ($interval->invert === 1) { + return FALSE; + } + } + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $entity = $this->entity; + parent::save($form, $form_state); + $form_state->setRedirect('entity.openy_ds_playlist_item.canonical', ['openy_ds_playlist_item' => $entity->id()]); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemModalForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemModalForm.php new file mode 100644 index 0000000..ba63836 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemModalForm.php @@ -0,0 +1,99 @@ +disableCache(); + + // TODO: in modals state API is broken. + $form['#prefix'] = '
    '; + $form['#suffix'] = '
    '; + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -100, + ]; + $form['actions']['submit']['#ajax'] = [ + 'callback' => [$this, 'originFormRefresh'], + 'progress' => ['type' => 'fullscreen'], + ]; + $form['actions']['cancel'] = [ + '#weight' => 10, + '#value' => $this->t('Cancel'), + '#name' => 'cancel', + '#type' => 'button', + '#limit_validation_errors' => [], + '#ajax' => [ + 'callback' => [$this, 'closeModal'], + 'progress' => ['type' => 'fullscreen'], + ], + ]; + $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; + unset($form['actions']['delete']); + + return $form; + } + + /** + * Ajax callback for cancel button. + */ + public static function closeModal(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + $response->addCommand(new CloseModalDialogCommand()); + return $response; + } + + /** + * Ajax callback for submit button. + */ + public static function originFormRefresh(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + if (!$form_state->hasAnyErrors()) { + $response->addCommand(new CloseModalDialogCommand()); + $response->addCommand(new InvokeCommand('.button-playlist_items-refresh', 'mousedown')); + } + else { + $response->addCommand(new ReplaceCommand('#playlist_item_modal_edit_form', $form)); + } + return $response; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state) { + $entity = $this->entity; + if ($entity->id()) { + parent::save($form, $form_state); + } + else { + $storage = $form_state->getStorage(); + if (!isset($storage['playlist'])) { + return; + } + + // Append new item to playlist and save. + $playlist = $storage['playlist']; + $playlist->get('field_items')->appendItem($entity); + $playlist->save(); + } + $form_state->disableRedirect(); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemSettingsForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemSettingsForm.php new file mode 100644 index 0000000..5aad00a --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistItemSettingsForm.php @@ -0,0 +1,53 @@ +t('Settings form for Digital Signage Playlist Item entities. Manage field settings here.'); + return $form; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistSettingsForm.php b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistSettingsForm.php new file mode 100644 index 0000000..949697e --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Form/OpenYPlaylistSettingsForm.php @@ -0,0 +1,53 @@ +t('Settings form for Digital Signage Playlist entities. Manage field settings here.'); + return $form; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/OpenYPlaylistAccessControlHandler.php b/modules/openy_digital_signage_playlist/src/OpenYPlaylistAccessControlHandler.php new file mode 100644 index 0000000..02d6562 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/OpenYPlaylistAccessControlHandler.php @@ -0,0 +1,47 @@ +isPublished()) { + return AccessResult::allowedIfHasPermission($account, 'view unpublished digital signage playlist entities'); + } + return AccessResult::allowedIfHasPermission($account, 'view published digital signage playlist entities'); + + case 'update': + return AccessResult::allowedIfHasPermission($account, 'edit digital signage playlist entities'); + + case 'delete': + return AccessResult::allowedIfHasPermission($account, 'delete digital signage playlist entities'); + } + + // Unknown operation, no opinion. + return AccessResult::neutral(); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'add digital signage playlist entities'); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/OpenYPlaylistItemAccessControlHandler.php b/modules/openy_digital_signage_playlist/src/OpenYPlaylistItemAccessControlHandler.php new file mode 100644 index 0000000..3f94f68 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/OpenYPlaylistItemAccessControlHandler.php @@ -0,0 +1,47 @@ +isPublished()) { + return AccessResult::allowedIfHasPermission($account, 'view unpublished digital signage playlist item entities'); + } + return AccessResult::allowedIfHasPermission($account, 'view published digital signage playlist item entities'); + + case 'update': + return AccessResult::allowedIfHasPermission($account, 'edit digital signage playlist item entities'); + + case 'delete': + return AccessResult::allowedIfHasPermission($account, 'delete digital signage playlist item entities'); + } + + // Unknown operation, no opinion. + return AccessResult::neutral(); + } + + /** + * {@inheritdoc} + */ + protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { + return AccessResult::allowedIfHasPermission($account, 'add digital signage playlist item entities'); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Plugin/Field/FieldWidget/EntityReferencePlaylistItemsWidget.php b/modules/openy_digital_signage_playlist/src/Plugin/Field/FieldWidget/EntityReferencePlaylistItemsWidget.php new file mode 100644 index 0000000..f05bf83 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Plugin/Field/FieldWidget/EntityReferencePlaylistItemsWidget.php @@ -0,0 +1,246 @@ +referencedEntities(); + $playlist = $items->getEntity(); + $referenced_entity = isset($referenced_entities[$delta]) ? $referenced_entities[$delta] : NULL; + if (!$referenced_entity) { + $element += [ + '#type' => 'value', + '#value' => NULL, + 'rendered_entity' => [ + '#markup' => t('You do not have any playlist items at the moment. Please add item.'), + ], + ]; + return ['target_id' => $element]; + } + + $ref_entity_id = $referenced_entity->id(); + $view_builder = \Drupal::entityTypeManager() + ->getViewBuilder($referenced_entity->getEntityTypeId()); + $element += [ + '#type' => 'value', + '#value' => $ref_entity_id, + ]; + + $form_element = [ + '#prefix' => "
    ", + '#suffix' => '
    ', + 'target_id' => $element, + '#entity' => $referenced_entity, + 'rendered_entity' => $view_builder->view($referenced_entity, 'teaser'), + 'item_actions' => [ + '#type' => 'container', + '#attributes' => ['class' => ['item-actions']], + '#weight' => 100, + 'edit' => [ + '#type' => 'link', + '#title' => t('Edit'), + '#url' => Url::fromRoute('openy_ds_playlist_item.modal_edit', [ + 'openy_ds_playlist_item' => $ref_entity_id, + 'js' => 'nojs', + ]), + '#attributes' => ['class' => ['use-ajax', 'button']], + ], + 'remove_button' => [ + '#type' => 'link', + '#title' => t('Remove'), + '#url' => Url::fromRoute('openy_ds_playlist_item.modal_remove', [ + 'openy_digital_signage_playlist' => $playlist->id(), + 'openy_ds_playlist_item' => $ref_entity_id, + 'js' => 'nojs', + ]), + '#attributes' => [ + 'class' => ['use-ajax', 'button'], + ], + ], + ], + '#attached' => ['library' => ['core/drupal.dialog.ajax']], + ]; + + return $form_element; + } + + /** + * {@inheritdoc} + */ + public function errorElement(array $element, ConstraintViolationInterface $error, array $form, FormStateInterface $form_state) { + return isset($element['target_id']) ? $element['target_id'] : FALSE; + } + + /** + * {@inheritdoc} + */ + protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) { + $playlist = $items->getEntity(); + $elements = parent::formMultipleElements($items, $form, $form_state); + + // If we're using ulimited cardinality we don't display one empty item. Form + // validation will kick in if left empty which esentially means people won't + // be able to submit w/o creating another entity. + $items_keys = Element::children($elements); + if (!$items->isEmpty()) { + foreach ($items_keys as $delta) { + $empty_item = isset($elements[$delta]['target_id']) && !isset($elements[$delta]['target_id']['#entity']) && !$elements[$delta]['target_id']['#value']; + if ($empty_item) { + // Remove empty or deleted item. + $elements['#max_delta'] = $elements['#max_delta'] - 1; + $items->removeItem($delta); + // Decrement the items count. + $field_state = static::getWidgetState([], $elements['#field_name'], $form_state); + $field_state['items_count']--; + static::setWidgetState([], $elements['#field_name'], $form_state, $field_state); + } + } + } + + // Rebuild elements based on updated items info. + $elements = parent::formMultipleElements($items, $form, $form_state); + // Fix dynamic id for wrapper div. + $new_wrapper_id = 'field-items-add-more-wrapper'; + $elements['#prefix'] = "
    "; + + $elements['add_more'] = [ + '#type' => 'container', + 'add_more' => [ + '#type' => 'link', + '#title' => t('Add item'), + '#url' => Url::fromRoute('openy_ds_playlist_item.modal_add', [ + 'openy_digital_signage_playlist' => $playlist->id(), + 'js' => 'nojs', + ]), + '#attributes' => [ + 'class' => [ + 'use-ajax', + 'button', + 'field-add-more-submit', + ], + ], + ], + 'refresh' => [ + '#type' => 'submit', + '#value' => $this->t('Refresh'), + '#name' => 'refresh-playlist-item', + '#submit' => [[get_class($this), 'refreshSubmit']], + '#attributes' => [ + 'class' => ['button-playlist_items-refresh', 'js-hide'], + ], + '#ajax' => [ + 'callback' => [get_class($this), 'ajaxItemRefreshCallback'], + 'wrapper' => $new_wrapper_id, + 'effect' => 'fade', + 'progress' => ['type' => 'none'], + ], + ], + ]; + $elements['assign_to_screen'] = [ + '#type' => 'container', + 'assign_to_screen' => [ + '#type' => 'link', + '#title' => t('Assign to screen'), + '#url' => Url::fromRoute('openy_ds_playlist_item.add_schedule_item', [ + 'openy_digital_signage_playlist' => $playlist->id(), + 'js' => 'nojs', + ]), + '#attributes' => [ + 'class' => [ + 'use-ajax', + 'button', + 'field-add-more-submit', + ], + ], + ], + ]; + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) { + if ($this->isDefaultValueWidget($form_state)) { + $items->filterEmptyItems(); + return; + } + + $field_name = $this->fieldDefinition->getName(); + $parents = array_merge($form['#parents'], [$field_name]); + $submitted_values = $form_state->getValue($parents); + $values = []; + foreach ($items as $delta => $value) { + $element = NestedArray::getValue($form, [$field_name, 'widget', $delta]); + $entity = isset($element['#entity']) ? $element['#entity'] : NULL; + $weight = isset($submitted_values[$delta]['_weight']) ? $submitted_values[$delta]['_weight'] : 0; + $values[$weight] = ['entity' => $entity]; + } + + // Sort items base on weights. + ksort($values); + $values = array_values($values); + + // Let the widget massage the submitted values. + $values = $this->massageFormValues($values, $form, $form_state); + + // Assign the values and remove the empty ones. + $items->setValue($values); + $items->filterEmptyItems(); + + // Put delta mapping in $form_state, so that flagErrors() can use it. + $field_name = $this->fieldDefinition->getName(); + $field_state = WidgetBase::getWidgetState($form['#parents'], $field_name, $form_state); + foreach ($items as $delta => $item) { + $field_state['original_deltas'][$delta] = isset($item->_original_delta) ? $item->_original_delta : $delta; + unset($item->_original_delta, $item->weight); + } + WidgetBase::setWidgetState($form['#parents'], $field_name, $form_state, $field_state); + } + + /** + * Ajax callback for the refresh item action. + */ + public static function ajaxItemRefreshCallback(array $form, FormStateInterface $form_state) { + $element = $form_state->getTriggeringElement(); + // Go 3 level up in the form, to the widgets container. + $array_parents = array_slice($element['#array_parents'], 0, -3); + $element = NestedArray::getValue($form, $array_parents); + + return $element; + } + + /** + * Submit callback for ajax buttons. + */ + public static function refreshSubmit(array $form, FormStateInterface &$form_state) { + $form_state->disableCache(); + $form_state->setRebuild(); + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Plugin/Field/FieldWidget/TimeOnlyWidget.php b/modules/openy_digital_signage_playlist/src/Plugin/Field/FieldWidget/TimeOnlyWidget.php new file mode 100644 index 0000000..37dadc2 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Plugin/Field/FieldWidget/TimeOnlyWidget.php @@ -0,0 +1,36 @@ +dateStorage->load('html_time')->getPattern(); + $element['value']['#date_time_element'] = 'time'; + $element['value']['#title'] = $this->fieldDefinition->getLabel(); + $element['value']['#description'] = $this->fieldDefinition->getDescription(); + $element["#theme_wrappers"] = []; + return $element; + } + +} diff --git a/modules/openy_digital_signage_playlist/src/Plugin/ScheduleItemDataType/Playlist.php b/modules/openy_digital_signage_playlist/src/Plugin/ScheduleItemDataType/Playlist.php new file mode 100644 index 0000000..301b199 --- /dev/null +++ b/modules/openy_digital_signage_playlist/src/Plugin/ScheduleItemDataType/Playlist.php @@ -0,0 +1,23 @@ + + {% if teaser %} + {# Teaser markup #} +
    +
    + {% if type == 'media' %} + {{- content.media -}} + {% else %} +
    + Playlist +
    + {% endif %} +
    +
    + {% if type == 'media' %} + {{- content.name -}} + {% else %} +
    + {{ entity.playlist.entity.name.value }} +
    + {% endif %} +
    +
    + {{- content.type -}} + + {% if type == 'playlist' %} + {% set count = entity.playlist.entity.field_items.getvalue|length %} + {% trans %} + ({{ count }} item) + {% plural count %} + ({{ count }} items) + {% endtrans %} + {% endif %} +
    +
    + {% if type == 'playlist' %} + {{ 'auto'|t }} + {% else %} + {{- content.duration -}} + {% endif %} +
    +
    + {% if entity.date_start.value %} + {{- content.date_start -}} + {% else %} + + {% endif %} +
    + {% if entity.date_end.value %} + {{- content.date_end -}} + {% else %} + + {% endif %} +
    +
    + {% if entity.time_start.value %} + {{- content.time_start -}} + {% else %} + + {% endif %} +
    + {% if entity.time_end.value %} + {{- content.time_end -}} + {% else %} + + {% endif %} +
    +
    + {% else %} + + {# Default markup #} + {% if content %} + {{- content -}} + {% endif %} + + {% endif %} + +
    diff --git a/modules/openy_digital_signage_playlist/templates/openy_digital_signage_playlist.html.twig b/modules/openy_digital_signage_playlist/templates/openy_digital_signage_playlist.html.twig new file mode 100644 index 0000000..18f5dd3 --- /dev/null +++ b/modules/openy_digital_signage_playlist/templates/openy_digital_signage_playlist.html.twig @@ -0,0 +1,22 @@ +{# +/** + * @file openy_digital_signage_playlist.html.twig + * Default theme implementation to present Digital Signage Playlist data. + * + * This template is used when viewing Digital Signage Playlist pages. + * + * + * Available variables: + * - content: A list of content items. Use 'content' to print all content, or + * - attributes: HTML attributes for the container element. + * + * @see template_preprocess_openy_digital_signage_playlist() + * + * @ingroup themeable + */ +#} + + {% if content %} + {{- content -}} + {% endif %} + diff --git a/modules/openy_digital_signage_playlist/templates/page--playlist--schedule-edit-form.html.twig b/modules/openy_digital_signage_playlist/templates/page--playlist--schedule-edit-form.html.twig new file mode 100644 index 0000000..b22373f --- /dev/null +++ b/modules/openy_digital_signage_playlist/templates/page--playlist--schedule-edit-form.html.twig @@ -0,0 +1,63 @@ +{# +/** + * @file + * Bartik's theme implementation to display a single page. + * + * The doctype, html, head and body tags are not in this template. Instead they + * can be found in the html.html.twig template normally located in the + * core/modules/system directory. + * + * Available variables: + * + * General utility variables: + * - base_path: The base URL path of the Drupal installation. Will usually be + * "/" unless you have installed Drupal in a sub-directory. + * - is_front: A flag indicating if the current page is the front page. + * - logged_in: A flag indicating if the user is registered and signed in. + * - is_admin: A flag indicating if the user has permission to access + * administration pages. + * + * Site identity: + * - front_page: The URL of the front page. Use this instead of base_path when + * linking to the front page. This includes the language domain or prefix. + * - logo: The url of the logo image, as defined in theme settings. + * - site_name: The name of the site. This is empty when displaying the site + * name has been disabled in the theme settings. + * - site_slogan: The slogan of the site. This is empty when displaying the site + * slogan has been disabled in theme settings. + + * Page content (in order of occurrence in the default page.html.twig): + * - node: Fully loaded node, if there is an automatically-loaded node + * associated with the page and the node ID is the second argument in the + * page's path (e.g. node/12345 and node/12345/revisions, but not + * comment/reply/12345). + * + * Regions: + * - page.header: Items for the header region. + * - page.highlighted: Items for the highlighted region. + * - page.primary_menu: Items for the primary menu region. + * - page.secondary_menu: Items for the secondary menu region. + * - page.featured_top: Items for the featured top region. + * - page.content: The main content of the current page. + * - page.sidebar_first: Items for the first sidebar. + * - page.sidebar_second: Items for the second sidebar. + * - page.featured_bottom_first: Items for the first featured bottom region. + * - page.featured_bottom_second: Items for the second featured bottom region. + * - page.featured_bottom_third: Items for the third featured bottom region. + * - page.footer_first: Items for the first footer column. + * - page.footer_second: Items for the second footer column. + * - page.footer_third: Items for the third footer column. + * - page.footer_fourth: Items for the fourth footer column. + * - page.footer_fifth: Items for the fifth footer column. + * - page.breadcrumb: Items for the breadcrumb region. + * + * @see template_preprocess_page() + * @see html.html.twig + */ +#} + +
    + {% block content %} + {{ page.content }} + {% endblock %} +
    diff --git a/modules/openy_digital_signage_playlist/templates/page--playlist.html.twig b/modules/openy_digital_signage_playlist/templates/page--playlist.html.twig new file mode 100644 index 0000000..aa3dd0c --- /dev/null +++ b/modules/openy_digital_signage_playlist/templates/page--playlist.html.twig @@ -0,0 +1,63 @@ +{# +/** + * @file + * Bartik's theme implementation to display a single page. + * + * The doctype, html, head and body tags are not in this template. Instead they + * can be found in the html.html.twig template normally located in the + * core/modules/system directory. + * + * Available variables: + * + * General utility variables: + * - base_path: The base URL path of the Drupal installation. Will usually be + * "/" unless you have installed Drupal in a sub-directory. + * - is_front: A flag indicating if the current page is the front page. + * - logged_in: A flag indicating if the user is registered and signed in. + * - is_admin: A flag indicating if the user has permission to access + * administration pages. + * + * Site identity: + * - front_page: The URL of the front page. Use this instead of base_path when + * linking to the front page. This includes the language domain or prefix. + * - logo: The url of the logo image, as defined in theme settings. + * - site_name: The name of the site. This is empty when displaying the site + * name has been disabled in the theme settings. + * - site_slogan: The slogan of the site. This is empty when displaying the site + * slogan has been disabled in theme settings. + + * Page content (in order of occurrence in the default page.html.twig): + * - node: Fully loaded node, if there is an automatically-loaded node + * associated with the page and the node ID is the second argument in the + * page's path (e.g. node/12345 and node/12345/revisions, but not + * comment/reply/12345). + * + * Regions: + * - page.header: Items for the header region. + * - page.highlighted: Items for the highlighted region. + * - page.primary_menu: Items for the primary menu region. + * - page.secondary_menu: Items for the secondary menu region. + * - page.featured_top: Items for the featured top region. + * - page.content: The main content of the current page. + * - page.sidebar_first: Items for the first sidebar. + * - page.sidebar_second: Items for the second sidebar. + * - page.featured_bottom_first: Items for the first featured bottom region. + * - page.featured_bottom_second: Items for the second featured bottom region. + * - page.featured_bottom_third: Items for the third featured bottom region. + * - page.footer_first: Items for the first footer column. + * - page.footer_second: Items for the second footer column. + * - page.footer_third: Items for the third footer column. + * - page.footer_fourth: Items for the fourth footer column. + * - page.footer_fifth: Items for the fifth footer column. + * - page.breadcrumb: Items for the breadcrumb region. + * + * @see template_preprocess_page() + * @see html.html.twig + */ +#} + +{% block content %} + {{ page.content }} +{% endblock %} + +{#{{ source("@ymca/scripts/optimizely.html") }}#} diff --git a/modules/openy_digital_signage_room/config/install/core.entity_form_display.openy_ds_room.openy_ds_room.default.yml b/modules/openy_digital_signage_room/config/install/core.entity_form_display.openy_ds_room.openy_ds_room.default.yml new file mode 100644 index 0000000..4f21bb3 --- /dev/null +++ b/modules/openy_digital_signage_room/config/install/core.entity_form_display.openy_ds_room.openy_ds_room.default.yml @@ -0,0 +1,68 @@ +uuid: d843c479-5029-45b1-ad24-48c9869b8fbc +langcode: en +status: true +dependencies: + config: + - field.field.openy_ds_room.openy_ds_room.field_room_origin + module: + - openy_digital_signage_room + - text +id: openy_ds_room.openy_ds_room.default +targetEntityType: openy_ds_room +bundle: openy_ds_room +mode: default +content: + description: + type: text_textarea + weight: 0 + region: content + settings: + placeholder: '' + rows: 5 + third_party_settings: { } + field_room_origin: + weight: 4 + settings: { } + third_party_settings: { } + type: openy_ds_room_origin_default + region: content + groupex_id: + type: string_textfield + weight: 5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + langcode: + type: language_select + weight: 2 + region: content + settings: + include_locked: true + third_party_settings: { } + location: + type: entity_reference_autocomplete + weight: 3 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } + personify_id: + type: string_textfield + weight: 6 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + status: + type: options_buttons + weight: 1 + region: content + settings: { } + third_party_settings: { } +hidden: + created: true diff --git a/modules/openy_digital_signage_room/config/install/core.entity_view_display.openy_ds_room.openy_ds_room.default.yml b/modules/openy_digital_signage_room/config/install/core.entity_view_display.openy_ds_room.openy_ds_room.default.yml new file mode 100644 index 0000000..2d4ac81 --- /dev/null +++ b/modules/openy_digital_signage_room/config/install/core.entity_view_display.openy_ds_room.openy_ds_room.default.yml @@ -0,0 +1,68 @@ +uuid: cc125ca6-8899-499c-8d78-a76334adb9d2 +langcode: en +status: true +dependencies: + config: + - field.field.openy_ds_room.openy_ds_room.field_room_origin + module: + - openy_digital_signage_room + - views_field_formatter +id: openy_ds_room.openy_ds_room.default +targetEntityType: openy_ds_room +bundle: openy_ds_room +mode: default +content: + field_room_origin: + type: openy_ds_room_origin_default + weight: 2 + region: content + label: above + settings: { } + third_party_settings: { } + groupex_id: + label: above + type: string + weight: 3 + region: content + settings: + link_to_entity: false + third_party_settings: { } + location: + label: above + type: views_field_formatter + weight: 1 + region: content + settings: + view: '' + arguments: + field_value: + checked: true + entity_id: + checked: true + delta: + checked: true + hide_empty: false + multiple: false + implode_character: '' + third_party_settings: { } + personify_id: + label: above + type: string + weight: 4 + region: content + settings: + link_to_entity: false + third_party_settings: { } + status: + label: above + type: boolean + weight: 0 + settings: + format: default + format_custom_false: '' + format_custom_true: '' + region: content + third_party_settings: { } +hidden: + description: true + langcode: true diff --git a/modules/openy_digital_signage_room/config/install/field.field.openy_ds_room.openy_ds_room.field_room_origin.yml b/modules/openy_digital_signage_room/config/install/field.field.openy_ds_room.openy_ds_room.field_room_origin.yml new file mode 100644 index 0000000..d01388c --- /dev/null +++ b/modules/openy_digital_signage_room/config/install/field.field.openy_ds_room.openy_ds_room.field_room_origin.yml @@ -0,0 +1,20 @@ +uuid: 669b241b-bea7-49a7-b5a9-3c20a21e6065 +langcode: en +status: true +dependencies: + config: + - field.storage.openy_ds_room.field_room_origin + module: + - openy_digital_signage_room +id: openy_ds_room.openy_ds_room.field_room_origin +field_name: field_room_origin +entity_type: openy_ds_room +bundle: openy_ds_room +label: 'Room Origin' +description: 'Stores Room Origin IDs. The field is a mapping, also the field is used for merging rooms.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: openy_ds_room_origin diff --git a/modules/openy_digital_signage_room/config/install/field.storage.openy_ds_room.field_room_origin.yml b/modules/openy_digital_signage_room/config/install/field.storage.openy_ds_room.field_room_origin.yml new file mode 100644 index 0000000..080bab3 --- /dev/null +++ b/modules/openy_digital_signage_room/config/install/field.storage.openy_ds_room.field_room_origin.yml @@ -0,0 +1,18 @@ +uuid: decb2f67-584f-4985-a4c7-1b674d59679d +langcode: en +status: true +dependencies: + module: + - openy_digital_signage_room +id: openy_ds_room.field_room_origin +field_name: field_room_origin +entity_type: openy_ds_room +type: openy_ds_room_origin +settings: { } +module: openy_digital_signage_room +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.info.yml b/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.info.yml new file mode 100644 index 0000000..ff18774 --- /dev/null +++ b/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.info.yml @@ -0,0 +1,8 @@ +name: Open Y Digital Signage Room integration with Open Y PEF +type: module +description: Open Y Digital Signage Room integration with Open Y PEF. +core: 8.x +package: "OpenY: Digital Signage" +dependencies: + - openy_digital_signage_room + - openy_repeat diff --git a/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.install b/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.install new file mode 100644 index 0000000..8fd60c8 --- /dev/null +++ b/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.install @@ -0,0 +1,22 @@ +select('repeat_event', 'r') + ->fields('r', ['location', 'room']) + ->distinct(true) + ->execute(); + + foreach ($room_data as $data) { + $room_manager->getOrCreateRoomByExternalId($data->room, $data->location, 'pef'); + } +} diff --git a/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.module b/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.module new file mode 100644 index 0000000..1ddfb20 --- /dev/null +++ b/modules/openy_digital_signage_room/modules/openy_ds_room_pef/openy_ds_room_pef.module @@ -0,0 +1,32 @@ +getEntityTypeId() != 'repeat') { + return; + } + + $room_manager = \Drupal::service('openy_digital_signage_room.manager'); + $room_manager->getOrCreateRoomByExternalId($entity->room->value, $entity->location->target_id, 'pef'); +} + +/** + * Implements hook_entity_update(). + */ +function openy_ds_room_pef_entity_update(EntityInterface $entity) { + if ($entity->getEntityTypeId() != 'repeat') { + return; + } + + $room_manager = \Drupal::service('openy_digital_signage_room.manager'); + $room_manager->getOrCreateRoomByExternalId($entity->room->value, $entity->location->target_id, 'pef'); +} diff --git a/modules/openy_digital_signage_room/openy_digital_signage_room.install b/modules/openy_digital_signage_room/openy_digital_signage_room.install new file mode 100644 index 0000000..36790e1 --- /dev/null +++ b/modules/openy_digital_signage_room/openy_digital_signage_room.install @@ -0,0 +1,22 @@ +setDirectory($config_dir); + $config_importer->importConfigs([ + 'core.entity_form_display.openy_ds_room.openy_ds_room.default', + 'core.entity_view_display.openy_ds_room.openy_ds_room.default', + 'field.field.openy_ds_room.openy_ds_room.field_room_origin', + 'field.storage.openy_ds_room.field_room_origin', + ]); +} diff --git a/modules/openy_digital_signage_room/openy_digital_signage_room.links.menu.yml b/modules/openy_digital_signage_room/openy_digital_signage_room.links.menu.yml index 9480807..724346d 100644 --- a/modules/openy_digital_signage_room/openy_digital_signage_room.links.menu.yml +++ b/modules/openy_digital_signage_room/openy_digital_signage_room.links.menu.yml @@ -7,6 +7,6 @@ openy_ds_room.admin.structure.settings: entity.openy_ds_room.collection: title: 'Rooms list' route_name: entity.openy_ds_room.collection - description: 'List Digital Signage Rooms' + description: 'A list of all rooms where activities are taking place, for use with room entry screens.' parent: system.admin_openy_digital_signage weight: 100 diff --git a/modules/openy_digital_signage_room/openy_digital_signage_room.module b/modules/openy_digital_signage_room/openy_digital_signage_room.module index 93e4a3f..90cac24 100644 --- a/modules/openy_digital_signage_room/openy_digital_signage_room.module +++ b/modules/openy_digital_signage_room/openy_digital_signage_room.module @@ -25,6 +25,13 @@ function openy_digital_signage_room_help($route_name, RouteMatchInterface $route } } +/** + * Implements hook_ds_version(). + */ +function openy_digital_signage_room_ds_version() { + return '0.0.1'; +} + /** * Implements hook_entity_create(). */ diff --git a/modules/openy_digital_signage_room/openy_digital_signage_room.post_update.php b/modules/openy_digital_signage_room/openy_digital_signage_room.post_update.php new file mode 100644 index 0000000..7e1d672 --- /dev/null +++ b/modules/openy_digital_signage_room/openy_digital_signage_room.post_update.php @@ -0,0 +1,49 @@ +count() + ->execute(); + } + + $ids = \Drupal::entityQuery('openy_ds_room') + ->condition('id', $sandbox['current'], '>') + ->range(0, 20) + ->sort('id') + ->execute(); + $rooms = \Drupal::entityTypeManager()->getStorage('openy_ds_room')->loadMultiple($ids); + foreach ($rooms as $room) { + if ($room->field_room_origin->isEmpty()) { + if (!$room->groupex_id->isEmpty()) { + $room->field_room_origin->appendItem([ + 'origin' => 'groupex', + 'id' => $room->groupex_id->value, + ]); + } + if (!$room->personify_id->isEmpty()) { + $room->field_room_origin->appendItem([ + 'origin' => 'personify', + 'id' => $room->personify_id->value, + ]); + } + } + $room->save(); + $sandbox['progress']++; + $sandbox['current'] = $room->id(); + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); + + return t('@count rooms have been migrated', ['@count' => $sandbox['progress']]); +} diff --git a/modules/openy_digital_signage_room/src/OpenYRoomManager.php b/modules/openy_digital_signage_room/src/OpenYRoomManager.php index bcb245e..7290158 100644 --- a/modules/openy_digital_signage_room/src/OpenYRoomManager.php +++ b/modules/openy_digital_signage_room/src/OpenYRoomManager.php @@ -3,11 +3,8 @@ namespace Drupal\openy_digital_signage_room; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\Entity\Query\QueryFactory; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -58,6 +55,15 @@ class OpenYRoomManager implements OpenYRoomManagerInterface { /** * Constructor. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger factory. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException */ public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerChannelFactoryInterface $logger_factory, ConfigFactoryInterface $config_factory) { $this->logger = $logger_factory->get(self::CHANNEL); @@ -65,31 +71,6 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Log $this->configFactory = $config_factory; } - /** - * Returns field name of the id field of the external system. - * - * @param string $type - * The type. - * - * @return bool|string - * The field name of FALSE. - */ - private function getFieldNameByType($type) { - $field_name = FALSE; - switch ($type) { - case 'groupex': - $field_name = 'groupex_id'; - break; - - case 'personify': - $field_name = 'personify_id'; - break; - - } - - return $field_name; - } - /** * Returns default room status by external system type. * @@ -101,7 +82,7 @@ private function getFieldNameByType($type) { */ private function getDefaultStatusByType($type) { if (!in_array($type, ['groupex', 'personify'])) { - return FALSE; + return TRUE; } $config = $this->configFactory->get(self::CONFIG); return $config->get($type == 'groupex' ? 'groupex_default_status' : 'personify_default_status'); @@ -111,17 +92,18 @@ private function getDefaultStatusByType($type) { * {@inheritdoc} */ public function getRoomByExternalId($id, $location_id, $type) { - if (!$id || !$location_id) { - return FALSE; - } - if (!$field_name = $this->getFieldNameByType($type)) { + if (!$location_id) { return FALSE; } - $entities = $this->storage->loadByProperties([ - $field_name => $id, - 'location' => $location_id, - ]); + $ids = $this->storage->getQuery() + ->condition('field_room_origin.origin', $type) + ->condition('field_room_origin.id', $id) + ->condition('location', $location_id) + ->sort('id') + ->execute(); + + $entities = $this->storage->loadMultiple($ids); return reset($entities); } @@ -130,6 +112,9 @@ public function getRoomByExternalId($id, $location_id, $type) { * {@inheritdoc} */ public function getOrCreateRoomByExternalId($id, $location_id, $type) { + if (!$id) { + $id = ''; + } $cache = &drupal_static('room_by_external_id', []); $cache_id = implode(':', [$id, $location_id, $type]); @@ -149,15 +134,10 @@ public function getOrCreateRoomByExternalId($id, $location_id, $type) { * {@inheritdoc} */ public function createRoomByExternalId($name, $location_id, $type) { - if (!$field_name = $this->getFieldNameByType($type)) { - $this->logger->warning('OpenYRoomManager is asked to created room with incorrect type @type. The name is @name, location id is @locationid', [ - '@type' => $type, - '@name' => $name, - '@locationid' => $location_id, - ]); - return FALSE; + $id = $name; + if (!$name) { + $name = $this->t('-- Not specified --'); } - $data = [ 'created' => REQUEST_TIME, 'title' => $name, @@ -168,7 +148,10 @@ public function createRoomByExternalId($name, $location_id, $type) { 'description' => $this->t('Automatically created during %type import', [ '%type' => $type, ]), - $field_name => $name, + 'field_room_origin' => [ + 'origin' => $type, + 'id' => $id, + ], ]; $room = $this->storage->create($data); diff --git a/modules/openy_digital_signage_room/src/Plugin/Field/FieldFormatter/RoomOriginFormatterDefault.php b/modules/openy_digital_signage_room/src/Plugin/Field/FieldFormatter/RoomOriginFormatterDefault.php new file mode 100644 index 0000000..d2333de --- /dev/null +++ b/modules/openy_digital_signage_room/src/Plugin/Field/FieldFormatter/RoomOriginFormatterDefault.php @@ -0,0 +1,70 @@ + $item) { + $elements[$delta] = [ + 'origin' => [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'class' => [ + 'field-origin', + ], + ], + '#value' => $item->origin, + ], + 'id' => [ + '#type' => 'html_tag', + '#tag' => 'div', + '#attributes' => [ + 'class' => [ + 'field-id', + ], + ], + '#value' => $item->id, + ], + ]; + } + return $elements; + } + +} diff --git a/modules/openy_digital_signage_room/src/Plugin/Field/FieldType/RoomOriginItem.php b/modules/openy_digital_signage_room/src/Plugin/Field/FieldType/RoomOriginItem.php new file mode 100644 index 0000000..b8e179d --- /dev/null +++ b/modules/openy_digital_signage_room/src/Plugin/Field/FieldType/RoomOriginItem.php @@ -0,0 +1,65 @@ +setLabel(t('Name of the origin')); + + $properties['id'] = DataDefinition::create('string') + ->setLabel(t('Origin ID')); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema['columns']['origin'] = [ + 'description' => 'Name of the origin.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ]; + + $schema['columns']['id'] = [ + 'description' => 'Origin ID', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $values = $this->getValue(); + return !($values['origin'] !== '' || $values['id'] !== ''); + } + +} diff --git a/modules/openy_digital_signage_room/src/Plugin/Field/FieldWidget/RoomOriginWidgetDefault.php b/modules/openy_digital_signage_room/src/Plugin/Field/FieldWidget/RoomOriginWidgetDefault.php new file mode 100644 index 0000000..02ccbbb --- /dev/null +++ b/modules/openy_digital_signage_room/src/Plugin/Field/FieldWidget/RoomOriginWidgetDefault.php @@ -0,0 +1,45 @@ +get($delta); + + $element['origin'] = [ + '#title' => t('Origin'), + '#type' => 'textfield', + '#default_value' => isset($item->origin) ? $item->origin : '', + '#description' => t('Example: pef.'), + ]; + + $element['id'] = [ + '#title' => t('ID'), + '#type' => 'textfield', + '#default_value' => isset($item->id) ? $item->id : '', + '#description' => t('Example: Studio A'), + ]; + + return $element; + } + +} diff --git a/modules/openy_digital_signage_schedule/config/install/views.view.digital_signage_schedule_items.yml b/modules/openy_digital_signage_schedule/config/install/views.view.digital_signage_schedule_items.yml index 92665c1..9d57571 100644 --- a/modules/openy_digital_signage_schedule/config/install/views.view.digital_signage_schedule_items.yml +++ b/modules/openy_digital_signage_schedule/config/install/views.view.digital_signage_schedule_items.yml @@ -4,6 +4,7 @@ dependencies: module: - datetime_range - ds_datetime_range + - dynamic_entity_reference - openy_digital_signage_schedule - user id: digital_signage_schedule_items @@ -204,10 +205,10 @@ display: entity_type: openy_digital_signage_sch_item entity_field: title plugin_id: field - content: - id: content + content_ref__target_id: + id: content_ref__target_id table: openy_digital_signage_sch_item - field: content + field: content_ref__target_id relationship: none group_type: group admin_label: '' @@ -244,7 +245,7 @@ display: element_class: '' element_label_type: '' element_label_class: '' - element_label_colon: false + element_label_colon: true element_wrapper_type: '' element_wrapper_class: '' element_default_classes: true @@ -253,10 +254,10 @@ display: empty_zero: false hide_alter_empty: true click_sort_column: target_id - type: entity_reference_label + type: dynamic_entity_reference_label settings: link: true - group_column: target_id + group_column: '' group_columns: { } group_rows: true delta_limit: 0 @@ -267,7 +268,7 @@ display: separator: ', ' field_api_classes: false entity_type: openy_digital_signage_sch_item - entity_field: content + entity_field: content_ref plugin_id: field time_slot__value: id: time_slot__value diff --git a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.info.yml b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.info.yml index e9fe612..f72cc43 100644 --- a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.info.yml +++ b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.info.yml @@ -8,3 +8,4 @@ dependencies: - ds_datetime_range - openy_digital_signage - openy_digital_signage_screen_content + - dynamic_entity_reference diff --git a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.install b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.install index 15d617c..3a25335 100644 --- a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.install +++ b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.install @@ -6,6 +6,9 @@ * Schedule module. */ +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\openy_digital_signage_schedule\Entity\OpenYScheduleItem; + /** * Update views config. */ @@ -17,3 +20,62 @@ function openy_digital_signage_schedule_update_8001() { 'views.view.digital_signage_schedule_items', ]); } + +/** + * Enable dynamic_entity_reference module and migrate data to content_ref. + */ +function openy_digital_signage_schedule_update_8002() { + \Drupal::service('module_installer')->install([ + 'dynamic_entity_reference', + ], TRUE); + + // Update Base Field Definitions for Schedule item entity. + // @see openy_digital_signage/modules/openy_digital_signage_schedule/src/Entity/OpenYScheduleItem.php + $old_content_definition = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('Content old')) + ->setRevisionable(TRUE) + ->setRequired(FALSE) + ->setDefaultValue(NULL) + ->setSetting('target_type', 'node') + ->setSetting('handler_settings', [ + 'target_bundles' => [ + 'screen_content' => 'screen_content', + ], + ]) + ->setTranslatable(FALSE) + ->setDisplayConfigurable('view', FALSE) + ->setDisplayConfigurable('form', FALSE); + + $new_content_definition = BaseFieldDefinition::create('dynamic_entity_reference') + ->setLabel(t('Content')) + ->setDescription(t('The Screen Content that is rotated for this time slot. If not exist - it will be created automatically.')) + ->setRevisionable(TRUE) + ->setRequired(TRUE) + ->setSettings(OpenYScheduleItem::getContentRefSettings()) + ->setTranslatable(FALSE) + ->setDisplayOptions('view', [ + 'label' => 'visible', + 'type' => 'label', + 'weight' => 3, + ]) + ->setDisplayOptions('form', [ + 'type' => 'dynamic_entity_reference_default', + 'weight' => 3, + 'settings' => [ + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'placeholder' => '', + ], + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + $update_manager = \Drupal::entityDefinitionUpdateManager(); + $update_manager->installFieldStorageDefinition('content', 'openy_digital_signage_sch_item', 'openy_digital_signage_schedule', $old_content_definition); + $update_manager->installFieldStorageDefinition('content_ref', 'openy_digital_signage_sch_item', 'openy_digital_signage_schedule', $new_content_definition); + + // Copy values from content to content_ref_target_id column. + $query = "UPDATE {openy_digital_signage_sch_item} SET content_ref__target_id=content, content_ref__target_type='node';"; + \Drupal::database()->query($query, [], []); + // TODO: update views here. +} diff --git a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.links.menu.yml b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.links.menu.yml index a229227..865e8a1 100644 --- a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.links.menu.yml +++ b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.links.menu.yml @@ -7,7 +7,7 @@ openy_digital_signage_schedule.admin.structure.settings: entity.openy_digital_signage_schedule.collection: title: 'Schedules' route_name: entity.openy_digital_signage_schedule.collection - description: 'List Schedule entities' + description: 'Assign a name to a schedule' parent: system.admin_openy_digital_signage weight: 100 @@ -20,6 +20,6 @@ openy_digital_signage_sch_item.admin.structure.settings: entity.openy_digital_signage_sch_item.collection: title: 'Schedule Items' route_name: entity.openy_digital_signage_sch_item.collection - description: 'List Schedule Item entities' + description: 'Create and link a schedule to a playlist and link it to a schedule name.' parent: system.admin_openy_digital_signage weight: 101 diff --git a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.routing.yml b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.routing.yml index 9a96937..3f77c0f 100644 --- a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.routing.yml +++ b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.routing.yml @@ -6,16 +6,6 @@ openy_digital_signage_schedule.new_screen_content_form: requirements: _permission: 'create screen_content content' -openy_digital_signage_schedule.new_screen_content_form_modal: - path: '/ajax/modal/schedule-item/new-screen-content' - defaults: - _title: 'New Screen Content' - _controller: '\Drupal\openy_digital_signage_schedule\Controller\ModalFormController::openModalForm' - requirements: - _permission: 'create screen_content content' - options: - _admin_route: TRUE - openy_digital_signage_schedule.settings: path: '/admin/digital-signage/settings/schedules' defaults: diff --git a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.services.yml b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.services.yml index 1a7b2db..366da73 100644 --- a/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.services.yml +++ b/modules/openy_digital_signage_schedule/openy_digital_signage_schedule.services.yml @@ -2,3 +2,6 @@ services: openy_digital_signage_schedule.manager: class: Drupal\openy_digital_signage_schedule\OpenYScheduleManager arguments: ['@entity.manager', '@entity.query', '@logger.factory'] + plugin.manager.schedule_item_data_type: + class: Drupal\openy_digital_signage_schedule\ScheduleItemDataTypePluginManager + parent: default_plugin_manager diff --git a/modules/openy_digital_signage_schedule/src/Annotation/ScheduleItemDataType.php b/modules/openy_digital_signage_schedule/src/Annotation/ScheduleItemDataType.php new file mode 100644 index 0000000..03ebc0f --- /dev/null +++ b/modules/openy_digital_signage_schedule/src/Annotation/ScheduleItemDataType.php @@ -0,0 +1,46 @@ +setDisplayConfigurable('form', TRUE); $fields['content'] = BaseFieldDefinition::create('entity_reference') - ->setLabel(t('Content')) - ->setDescription(t('The Screen Content that is rotated for this time slot.')) + ->setLabel(t('Content old')) ->setRevisionable(TRUE) - ->setRequired(TRUE) + ->setRequired(FALSE) + ->setDefaultValue(NULL) ->setSetting('target_type', 'node') ->setSetting('handler_settings', [ 'target_bundles' => [ @@ -247,14 +249,26 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ], ]) ->setTranslatable(FALSE) + ->setDisplayConfigurable('view', FALSE) + ->setDisplayConfigurable('form', FALSE); + + $fields['content_ref'] = BaseFieldDefinition::create('dynamic_entity_reference') + ->setLabel(t('Content')) + ->setDescription(t('Choose the content name that you would like to assign to this time slot. If you want to create a new content, just enter a unique title and save the form.')) + ->setRevisionable(TRUE) + ->setRequired(TRUE) + ->setSettings(self::getContentRefSettings()) + ->setTranslatable(FALSE) ->setDisplayOptions('view', [ 'label' => 'visible', - 'type' => 'node', - 'weight' => 2, + 'type' => 'label', + // TODO: add view mode setting for each entity type. + // (maybe store this info inside plugin). + 'weight' => 1, ]) ->setDisplayOptions('form', [ - 'type' => 'entity_reference_autocomplete', - 'weight' => 2, + 'type' => 'dynamic_entity_reference_si', + 'weight' => 1, 'settings' => [ 'match_operator' => 'CONTAINS', 'size' => '60', @@ -267,4 +281,32 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { return $fields; } + /** + * Helper function for field content_ref settings. + */ + public static function getContentRefSettings() { + $plugin_definitions = \Drupal::service('plugin.manager.schedule_item_data_type')->getDefinitions(); + $settings = ['exclude_entity_types' => FALSE]; + $entity_type_settings = []; + + foreach ($plugin_definitions as $definition) { + $settings['entity_type_ids'][$definition['entity_type']] = $definition['entity_type']; + $entity_type_settings[$definition['entity_type']] = [ + 'handler' => 'default:' . $definition['entity_type'], + 'handler_settings' => ['auto_create' => TRUE], + ]; + if (isset($definition['entity_bundle'])) { + $entity_type_settings[$definition['entity_type']]['handler_settings'] += [ + 'target_bundles' => [ + $definition['entity_bundle'] => $definition['entity_bundle'], + ], + 'auto_create_bundle' => $definition['entity_bundle'], + ]; + } + } + + $settings += $entity_type_settings; + return $settings; + } + } diff --git a/modules/openy_digital_signage_schedule/src/Form/OpenYScheduleItemForm.php b/modules/openy_digital_signage_schedule/src/Form/OpenYScheduleItemForm.php index c12b895..0f4af7a 100644 --- a/modules/openy_digital_signage_schedule/src/Form/OpenYScheduleItemForm.php +++ b/modules/openy_digital_signage_schedule/src/Form/OpenYScheduleItemForm.php @@ -46,21 +46,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { if (in_array($route_name, ['screen_schedule.edit_schedule_item', 'screen_schedule.add_schedule_item'])) { $form_state->addBuildInfo('screen', $this->getRequest()->get('screen')); - // Add new screen content link. - $form['content']['widget'][0]['new_content'] = [ - '#type' => 'link', - '#title' => $this->t('New screen content'), - '#url' => Url::fromRoute('openy_digital_signage_schedule.new_screen_content_form_modal'), - '#attributes' => [ - 'class' => [ - 'use-ajax', - 'new-screen-content-link', - ], - ], - '#attached' => [ - 'library' => ['core/drupal.dialog.ajax'], - ], - ]; $form['actions']['submit']['#ajax'] = [ 'callback' => '::ajaxFormSubmitHandler', @@ -100,14 +85,15 @@ public function addNewScreenContentCallback(array &$form, FormStateInterface $fo */ public function ajaxFormSubmitHandler(array &$form, FormStateInterface $form_state) { $schedule_item = $this->entity; - $screen_content = $schedule_item->content->entity; + $screen_content = $schedule_item->content_ref->entity; $screen = $form_state->getBuildInfo()['screen']; + $screen_content_entity_type = $screen_content->getEntityTypeId(); // Build an edit Schedule item form. $build = [ '#type' => 'container', '#tag' => 'div', '#attributes' => [ - 'data-src' => Url::fromRoute('entity.node.canonical', ['node' => $screen_content->id()]) + 'data-src' => Url::fromRoute("entity.$screen_content_entity_type.canonical", [$screen_content_entity_type => $screen_content->id()]) ->toString(), 'class' => ['frame-container'], ], diff --git a/modules/openy_digital_signage_schedule/src/OpenYScheduleManager.php b/modules/openy_digital_signage_schedule/src/OpenYScheduleManager.php index b4b4c76..48601c7 100644 --- a/modules/openy_digital_signage_schedule/src/OpenYScheduleManager.php +++ b/modules/openy_digital_signage_schedule/src/OpenYScheduleManager.php @@ -217,7 +217,7 @@ public function getScreenUpcomingScreenContents(OpenYScreen $screen, $timespan, $schedule = $this->getUpcomingScreenContents($screen->screen_schedule->entity, $timespan, $now); foreach ($schedule as &$item) { - $item['screen_content'] = $item['item']->content->entity; + $item['screen_content'] = $item['item']->content_ref->entity; $item['id'] = $item['item']->id(); $item['type'] = 'schedule_item'; } @@ -249,6 +249,9 @@ public function getScreenUpcomingScreenContents(OpenYScreen $screen, $timespan, foreach ($schedule as $item) { $item_type = $item['override'] ? 'override' : 'regular'; + if ($item['screen_content']->status->value == 0) { + continue; + } // Search for the intersections with existing slots. $intersections = []; diff --git a/modules/openy_digital_signage_schedule/src/Plugin/Field/FieldWidget/DynamicEntityReferenceSIWidget.php b/modules/openy_digital_signage_schedule/src/Plugin/Field/FieldWidget/DynamicEntityReferenceSIWidget.php new file mode 100644 index 0000000..f4850d1 --- /dev/null +++ b/modules/openy_digital_signage_schedule/src/Plugin/Field/FieldWidget/DynamicEntityReferenceSIWidget.php @@ -0,0 +1,74 @@ +disableCache(); + + // Render field as fieldset. + $form_element['#type'] = 'fieldset'; + // Remove container-inline class. + unset($form_element['#attributes']); + + $parents = $form['#parents']; + $settings = $this->getFieldSettings(); + $field_name = $this->fieldDefinition->getName(); + $id_prefix = implode('-', array_merge($parents, [$field_name])); + $wrapper_id = Html::getUniqueId($id_prefix . '-wrapper'); + $form_element['#prefix'] = '
    '; + $form_element['#suffix'] = '
    '; + + $trigger_element = $form_state->getTriggeringElement(); + if ($trigger_element && isset($trigger_element['#name']) && strpos($trigger_element["#name"], 'target_type') !== FALSE) { + $target_type = $trigger_element['#value']; + $form_element['target_id']['#target_type'] = $target_type; + $form_element['target_id']['#selection_handler'] = $settings[$target_type]['handler']; + $form_element['target_id']['#selection_settings'] = $settings[$target_type]['handler_settings']; + $form_element['target_id']['#value'] = ''; + } + + $form_element['target_type']['#ajax'] = [ + 'callback' => [get_class($this), 'entityTypeSwitch'], + 'wrapper' => $wrapper_id, + 'effect' => 'fade', + ]; + + return $form_element; + } + + /** + * Ajax callback for the "target_type" field. + */ + public static function entityTypeSwitch(array $form, FormStateInterface $form_state) { + $element = $form_state->getTriggeringElement(); + + // Go one level up in the form, to the widgets container. + $element = NestedArray::getValue($form, array_slice($element['#array_parents'], 0, -1)); + return $element; + } + +} diff --git a/modules/openy_digital_signage_schedule/src/Plugin/ScheduleItemDataType/ScreenContent.php b/modules/openy_digital_signage_schedule/src/Plugin/ScheduleItemDataType/ScreenContent.php new file mode 100644 index 0000000..047a402 --- /dev/null +++ b/modules/openy_digital_signage_schedule/src/Plugin/ScheduleItemDataType/ScreenContent.php @@ -0,0 +1,24 @@ +get('id'); + } + + /** + * {@inheritdoc} + */ + public function getLabel() { + return $this->get('label'); + } + + /** + * {@inheritdoc} + */ + public function getEntityType() { + return $this->get('entity_type'); + } + + /** + * {@inheritdoc} + */ + public function getEntityBundle() { + return $this->get('entity_bundle'); + } + + /** + * {@inheritdoc} + */ + public function get($key) { + if (!empty($this->configuration[$key])) { + return $this->configuration[$key]; + } + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) { + $this->configuration[$key] = $value; + } + +} diff --git a/modules/openy_digital_signage_schedule/src/ScheduleItemDataTypePluginInterface.php b/modules/openy_digital_signage_schedule/src/ScheduleItemDataTypePluginInterface.php new file mode 100644 index 0000000..359b011 --- /dev/null +++ b/modules/openy_digital_signage_schedule/src/ScheduleItemDataTypePluginInterface.php @@ -0,0 +1,78 @@ +alterInfo('schedule_item_data_type_info'); + $this->setCacheBackend($cache_backend, 'schedule_item_data_type_plugins'); + } + +} diff --git a/modules/openy_digital_signage_screen/css/openy-digital-signage-screen.css b/modules/openy_digital_signage_screen/css/openy-digital-signage-screen.css index eb59c62..c5ac813 100644 --- a/modules/openy_digital_signage_screen/css/openy-digital-signage-screen.css +++ b/modules/openy_digital_signage_screen/css/openy-digital-signage-screen.css @@ -40,7 +40,6 @@ text-align: center; text-transform: uppercase; top: 36.2vw; - -webkit-transition: opacity 0.5s; transition: opacity 0.5s; width: 100%; } @@ -63,7 +62,6 @@ top: 10.4vw; left: 50%; margin-left: -42.5vw; - -webkit-transition: top 2s, width 2s, height 2s, margin-left 2s, opacity 2s, background-color 1s; transition: top 2s, width 2s, height 2s, margin-left 2s, opacity 2s, background-color 1s; z-index: 10; } .class .trainer { @@ -72,7 +70,6 @@ text-align: center; font-size: 2.45vw; padding-top: 3.9vw; - -webkit-transition: font-size 2s, padding-top 2s; transition: font-size 2s, padding-top 2s; position: relative; } .class .trainer .trainer__substitute { @@ -95,7 +92,6 @@ margin: auto; padding: 0 1.5vw 0; text-align: center; - -webkit-transition: font-size 2s, padding-top 2s; transition: font-size 2s, padding-top 2s; } .class .class-name sup { font-size: 2vw; @@ -106,7 +102,6 @@ text-transform: none; margin-top: 2.5vw; opacity: 1; - -webkit-transition: opacity 0.5s; transition: opacity 0.5s; } .class .class-time-frame-from { color: #00aeef; @@ -121,7 +116,6 @@ left: 72%; margin-top: -1.5vw; opacity: 1; - -webkit-transition: opacity 1s; transition: opacity 1s; } .class .class-time-frame-progress { width: 41%; @@ -132,7 +126,6 @@ margin: auto; overflow: hidden; opacity: 1; - -webkit-transition: opacity 1s; transition: opacity 1s; } .class .class-time-frame-progress-bar { background-color: #00aeef; @@ -143,7 +136,6 @@ height: 100%; } .class .class-time-countdown { opacity: 0; - -webkit-transition: opacity 0.5s; transition: opacity 0.5s; } .class .class-time-countdown .class-time-countdown-background { background-color: white; @@ -216,7 +208,6 @@ display: inline-block; opacity: 1; text-align: center; - -webkit-transition: opacity 0.5s; transition: opacity 0.5s; width: 1.04167vw; } .class .class-time-countdown .class-time-countdown-time--value .separator.odd { @@ -234,7 +225,6 @@ .class-next, .class-prev { opacity: 1; - -webkit-transition: opacity 0.5s; transition: opacity 0.5s; } .class-next .class, .class-prev .class { diff --git a/modules/openy_digital_signage_screen/css/screen-schedule.css b/modules/openy_digital_signage_screen/css/screen-schedule.css index 095f0e9..fe7f08d 100644 --- a/modules/openy_digital_signage_screen/css/screen-schedule.css +++ b/modules/openy_digital_signage_screen/css/screen-schedule.css @@ -1,29 +1,24 @@ .screen-schedule-ui { - display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; - -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; - -webkit-box-align: stretch; -webkit-align-items: stretch; -ms-flex-align: stretch; align-items: stretch; } .screen-schedule-ui .screen-schedule-ui--left, .screen-schedule-ui .screen-schedule-ui--right { - -webkit-box-flex: 0; -webkit-flex: 0 1 66%; -ms-flex: 0 1 66%; flex: 0 1 66%; position: relative; overflow: hidden; } .screen-schedule-ui .screen-schedule-ui--left { - -webkit-box-flex: 0; -webkit-flex: 0 1 33%; -ms-flex: 0 1 33%; flex: 0 1 33%; @@ -33,14 +28,12 @@ position: relative; } .screen-schedule-timeline { - display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; - -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; @@ -55,7 +48,6 @@ box-shadow: 3px 3px 4px rgba(0, 0, 0, 0.25); z-index: 11; } .screen-schedule-timeline .screen-schedule-timeline__hours { - -webkit-box-flex: 0; -webkit-flex: 0 1 20%; -ms-flex: 0 1 20%; flex: 0 1 20%; @@ -74,7 +66,6 @@ bottom: 0; width: 100%; } .screen-schedule-timeline .screen-schedule-timeline__schedule-items { - -webkit-box-flex: 0; -webkit-flex: 0 1 80%; -ms-flex: 0 1 80%; flex: 0 1 80%; @@ -91,7 +82,6 @@ opacity: 0.85; cursor: pointer; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5); - -webkit-transition: opacity 200ms; transition: opacity 200ms; min-height: 30px; } .screen-schedule-timeline .screen-schedule-item:hover { @@ -162,7 +152,6 @@ -webkit-transform: translateX(50%) translateY(50%); -ms-transform: translateX(50%) translateY(50%); transform: translateX(50%) translateY(50%); - -webkit-transition: width 200ms, height 200ms, opacity 200ms; transition: width 200ms, height 200ms, opacity 200ms; width: 40px; z-index: 100; } diff --git a/modules/openy_digital_signage_screen/css/screen.css b/modules/openy_digital_signage_screen/css/screen.css index 66c675b..f55382a 100644 --- a/modules/openy_digital_signage_screen/css/screen.css +++ b/modules/openy_digital_signage_screen/css/screen.css @@ -186,7 +186,6 @@ table { display: none !important; } .decor--flex, .header--inner, .block-table .row, .page-entry .page-content, .page-split .page-content, .content-coll, .section--small-banners, .block-banner, .block-event .content, .block-event .wrapper-info, .block-event .wrapper-time { - display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; } @@ -197,70 +196,56 @@ table { flex-wrap: wrap; } .decor--justify-start { - -webkit-box-pack: start; -webkit-justify-content: flex-start; -ms-flex-pack: start; justify-content: flex-start; } .decor--justify-end, .block-event .content { - -webkit-box-pack: end; -webkit-justify-content: flex-end; -ms-flex-pack: end; justify-content: flex-end; } .decor--justify-center, .block-event .wrapper-info, .block-event .wrapper-time { - -webkit-box-pack: center; -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; } .decor--justify-between, .header--inner, .block-table .row, .content-coll--big, .block-banner--footer .block-event .content { - -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; } .decor--align-start { - -webkit-box-align: start; -webkit-align-items: flex-start; -ms-flex-align: start; align-items: flex-start; } .decor--align-end, .block-banner { - -webkit-box-align: end; -webkit-align-items: flex-end; -ms-flex-align: end; align-items: flex-end; } .decor--align-center { - -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center; } .decor--align-between { - -webkit-box-align: space-between; -webkit-align-items: space-between; -ms-flex-align: space-between; align-items: space-between; } .decor--direction-column, .page-entry .page-content, .content-coll--big, .block-event .wrapper-info, .block-event .wrapper-time { - -webkit-box-orient: vertical; - -webkit-box-direction: normal; -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; } .decor--row-reverse, .block-event .content { - -webkit-box-orient: horizontal; - -webkit-box-direction: reverse; -webkit-flex-direction: row-reverse; -ms-flex-direction: row-reverse; flex-direction: row-reverse; } .decor--row-initial, .block-banner--footer .block-event .content { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; } @@ -406,12 +391,14 @@ table { content: "\e927"; } body.node--type-screen-content, -body.page-node-type-screen-content { +body.page-node-type-screen-content, +body.ds-screen.ds-screen-full-size { padding-top: 0 !important; } + body.node--type-screen-content .openy-page-tabs, + body.page-node-type-screen-content .openy-page-tabs, + body.ds-screen.ds-screen-full-size .openy-page-tabs { + display: none; } -body.node--type-screen-content .openy-page-tabs, -body.page-node-type-screen-content .openy-page-tabs { - display: none; } .progress--small { background: #6d4e92; width: 100%; @@ -456,7 +443,6 @@ body.page-node-type-screen-content .openy-page-tabs { max-height: 90vh; } .openy-ds-layout .ipe-actions { - display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex; @@ -502,7 +488,6 @@ body.page-node-type-screen-content .openy-page-tabs { .openy-ds-layout .block-region-ticker > .ipe-actions { margin-top: 0; - -webkit-transition: margin-top 400ms; transition: margin-top 400ms; } .openy-ds-layout .block-region-ticker:hover > .ipe-actions { @@ -521,7 +506,6 @@ body.page-node-type-screen-content .openy-page-tabs { #panels-ipe-tray { bottom: -30px; - -webkit-transition: bottom 0.4s; transition: bottom 0.4s; } #panels-ipe-tray:hover { bottom: 0; } @@ -891,7 +875,8 @@ body.page-node-type-screen-content .openy-page-tabs { .ds-screen.ds-screen-full-size.user-logged-in .screen.screen-active { cursor: default; } -.ds-screen.ds-screen-full-size #block-tabs, .ds-screen.ds-screen-full-size .openy-page-tabs { +.ds-screen #block-tabs, +.ds-screen .openy-page-tabs { display: none; } .screen { @@ -1381,7 +1366,6 @@ body.page-node-type-screen-content .openy-page-tabs { height: 100%; width: 100%; position: absolute; - -webkit-transition: top 0.5s; transition: top 0.5s; } .block-class-ticker .class-active { top: 0; } @@ -1490,7 +1474,6 @@ body.page-node-type-screen-content .openy-page-tabs { position: absolute; right: 0; top: 0; - -webkit-transition: width 0.5s, font-size 0.5s; transition: width 0.5s, font-size 0.5s; width: 45vw; } .class-active .block-class-ticker__content .class .class-time-frame .class-time-frame-from, @@ -1505,7 +1488,6 @@ body.page-node-type-screen-content .openy-page-tabs { -ms-transform: translateY(-50%); transform: translateY(-50%); margin: 0; - -webkit-transition: font-size 0.5s, left 0.5s, right 0.5s; transition: font-size 0.5s, left 0.5s, right 0.5s; } .class-active .block-class-ticker__content .class .class-time-frame .class-time-frame-to, .class-next .block-class-ticker__content .class .class-time-frame .class-time-frame-to, @@ -1701,3 +1683,125 @@ body.page-node-type-screen-content .openy-page-tabs { .openy-res-layout.scheme-purple .class-next .block-class-ticker__content .class.class-awaiting .class-time-frame, .openy-res-layout.scheme-purple .class-prev .block-class-ticker__content .class.class-awaiting .class-time-frame { background-color: #92278f; } + +.block-complete-schedule { + background-color: #636466; + font-family: "Cachet-Book"; + font-size: 1.625vw; + line-height: 1.2; } + +.schedule-header { + background-color: #383838; + color: #fff; } + +.schedule-table { + color: #fff; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + min-height: 100vh; } + +.schedule-table-ongoing, +.schedule-table-upcoming { + font-weight: 700; + font-size: 150%; + text-align: center; + text-transform: uppercase; } + +.schedule-table-ongoing { + -webkit-order: -2; + -ms-flex-order: -2; + order: -2; } + +.schedule-table-upcoming { + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; } + +.schedule-row { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + position: relative; } + +.schedule-row-class { + border-bottom: 0.5px solid rgba(0, 0, 0, 0.25); + border-top: 0.5px solid rgba(0, 0, 0, 0.25); + -webkit-order: 2; + -ms-flex-order: 2; + order: 2; + transition: height 500ms; } + +.schedule-row-class-past { + height: 0; + border: none; } + +.schedule-row-class-ongoing { + -webkit-order: -1; + -ms-flex-order: -1; + order: -1; } + +.schedule-row-column { + display: block; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-flex-grow: 2; + -ms-flex-positive: 2; + flex-grow: 2; + overflow: hidden; + padding: 0.1vw 0.8vw; + text-overflow: ellipsis; + white-space: nowrap; + z-index: 2; } + +.class-trainer { + text-align: center; } + +.class-name { + -webkit-flex-grow: 5; + -ms-flex-positive: 5; + flex-grow: 5; } + +.class-time-frame { + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + padding-right: 0; + text-align: right; } + +.class-duration { + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; } + +span.class-duration:before { + content: "("; + padding-right: 0.2vw; } + +span.class-duration:after { + content: ")"; + padding-left: 0.2vw; } + +.class-room { + -webkit-flex-grow: 3; + -ms-flex-positive: 3; + flex-grow: 3; } + +.class-progress { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; } + +.class-progress-bar { + background-color: rgba(0, 96, 175, 0.5); + height: 100%; + left: 0; + top: 0; + width: 0%; + z-index: 1; } diff --git a/modules/openy_digital_signage_screen/js/screen-handler.js b/modules/openy_digital_signage_screen/js/screen-handler.js index 98a39c3..9cf115b 100644 --- a/modules/openy_digital_signage_screen/js/screen-handler.js +++ b/modules/openy_digital_signage_screen/js/screen-handler.js @@ -159,7 +159,6 @@ function TimeManager() { this.element.data('screen', self); this.init = function() { - self.loopCallback(); self.loop = setInterval(self.loopCallback, self.options.screenUpdatePeriod); self.element .css({opacity: 0, display: 'block'}) @@ -167,6 +166,7 @@ function TimeManager() { self.afterInit(); }); $("body > .loader").fadeOut(self.options.animation); + setTimeout(function () { self.loopCallback(); }, 0); }; this.afterInit = function() { @@ -192,7 +192,14 @@ function TimeManager() { var screenContent = ObjectsManager.getObject(this); var workingHours = screenContent.getWorkingHours(); if (time >= workingHours.from && time < workingHours.to) { - screenContent.activate(); + if (screenContent.isEmpty()) { + screenContent.hibernate(); + self.activateFallbackContent(); + } + else { + screenContent.activate(); + self.deactivateFallbackContent(); + } } else { screenContent.deactivate(); @@ -274,7 +281,7 @@ function TimeManager() { }); } Drupal.attachBehaviors(self.element); - self.updateScreenContents(); + setTimeout(function () { self.updateScreenContents(); }, 1); }); }; @@ -286,15 +293,32 @@ function TimeManager() { return self.element.data('app-version'); }; + this.activateFallbackContent = function () { + var screenContents = self.getScreenContents(); + if (screenContents.filter('.screen-content-active-fallback').length > 0) { + return; + } + let fallback = screenContents.filter('.screen-content--fallback').first(); + fallback = ObjectsManager.getObject(fallback); + fallback.activateAsFallbackContent(); + }; + + this.deactivateFallbackContent = function () { + var screenContents = self.getScreenContents(); + let fallback = screenContents.filter('.screen-content-active-fallback').first(); + if (fallback.length > 0) { + fallback = ObjectsManager.getObject(fallback); + fallback.deactivate(true); + } + }; + return this; } function OpenYScreenContent(el) { var self = this; this.options = { animation: 1000 }; - // Store element. this.element = $(el); - // Store object. this.element.data('screenContent', this); this.getBlocks = function() { @@ -305,8 +329,23 @@ function TimeManager() { return self.element.data('screen-content-id'); }; + this.isEmpty = function () { + let isEmpty = true; + self.getBlocks().each(function (i) { + var block = ObjectsManager.getObject(this); + if (block.hasOwnProperty('isEmpty')) { + isEmpty = isEmpty && block.isEmpty(); + } + else { + isEmpty = false; + } + }); + + return isEmpty; + }; + this.activate = function() { - if (self.isActive()) { + if (self.isActive() && !self.isHibernated()) { return; } self.element @@ -317,6 +356,7 @@ function TimeManager() { .css({position: 'static'}) .removeClass('screen-content-inactive') .removeClass('screen-content-activating') + .removeClass('screen-content-hibernate') .addClass('screen-content-active'); }); self.getBlocks().each(function () { @@ -325,6 +365,28 @@ function TimeManager() { }); }; + this.activateAsFallbackContent = function() { + if (self.isActive() || self.isActiveFallback()) { + return; + } + self.element + .css({position: 'absolute', top: '100vh'}) + .addClass('screen-content-activating') + .animate({top: 0}, self.options.animation, function () { + $(this) + .css({position: 'static'}) + .removeClass('screen-content-inactive') + .removeClass('screen-content-activating') + .removeClass('screen-content-hibernate') + .addClass('screen-content-active') + .addClass('screen-content-active-fallback'); + }); + self.getBlocks().each(function () { + var block = ObjectsManager.getObject(this); + block.activate(); + }); + }; + this.activateNoDelay = function() { if (self.isActive()) { return; @@ -336,6 +398,7 @@ function TimeManager() { self.element .removeClass('screen-content-inactive') .removeClass('screen-content-activating') + .removeClass('screen-content-hibernate') .addClass('screen-content-active'); }; @@ -347,11 +410,35 @@ function TimeManager() { var block = ObjectsManager.getObject(this); block.deactivate(); }); - self.element.remove(); + if (!self.isActiveFallback()) { + self.element.remove(); + } + else { + self.element + .removeClass('screen-content-active') + .removeClass('screen-content-active-fallback'); + } }; - this.deactivate = function() { - if (!self.isActive()) { + /** + * Temprary disable the screen content. + */ + this.hibernate = function() { + if (!self.isActive() || self.isHibernated()) { + return; + } + + self.element + .css({position: 'absolute', top: 0}) + .animate({ top: '-100vh' }, self.options.animation, function() { + $(this) + // .removeClass('screen-content-active') + .addClass('screen-content-hibernate'); + }); + }; + + this.deactivate = function(deactivateFallback = false) { + if (!self.isActive() || (self.isActiveFallback() && !deactivateFallback)) { return; } @@ -360,8 +447,13 @@ function TimeManager() { .animate({ top: '-100vh' }, self.options.animation, function() { $(this) .removeClass('screen-content-active') - .addClass('screen-content-inactive') - .remove(); + .addClass('screen-content-inactive'); + if (self.isActiveFallback()) { + $(this).removeClass('screen-content-active-fallback'); + } + else { + $(this).remove(); + } }); self.getBlocks().each(function() { var block = ObjectsManager.getObject(this); @@ -373,6 +465,14 @@ function TimeManager() { return self.element.hasClass('screen-content-active'); }; + this.isActiveFallback = function() { + return self.element.hasClass('screen-content-active-fallback'); + }; + + this.isHibernated = function() { + return self.element.hasClass('screen-content-hibernate'); + }; + this.getWorkingHours = function() { return { from: self.element.data('from-ts'), @@ -385,9 +485,7 @@ function TimeManager() { function OpenYScreenContentBlock(el) { var self = this; - // Store element. this.element = $(el); - // Store object. this.element.data('screenContentBlock', this); this.getBlocks = function() { @@ -438,6 +536,7 @@ function TimeManager() { */ Drupal.behaviors.screen_handler = { attach: function (context, settings) { + $('body.screen').removeClass('screen'); $('.screen', context).once().each(function () { var screen = new OpenYScreen(this); screen.init(); diff --git a/modules/openy_digital_signage_screen/openy_digital_signage_screen.links.menu.yml b/modules/openy_digital_signage_screen/openy_digital_signage_screen.links.menu.yml index 89ed80d..e950c8c 100644 --- a/modules/openy_digital_signage_screen/openy_digital_signage_screen.links.menu.yml +++ b/modules/openy_digital_signage_screen/openy_digital_signage_screen.links.menu.yml @@ -1,7 +1,7 @@ entity.openy_digital_signage_screen.collection: title: 'Screens' route_name: entity.openy_digital_signage_screen.collection - description: 'List Screen entities' + description: 'Link a schedule to a specific screen within the YMCA.' parent: system.admin_openy_digital_signage weight: 100 diff --git a/modules/openy_digital_signage_screen/openy_digital_signage_screen.module b/modules/openy_digital_signage_screen/openy_digital_signage_screen.module index fe03a32..bc5b7b0 100644 --- a/modules/openy_digital_signage_screen/openy_digital_signage_screen.module +++ b/modules/openy_digital_signage_screen/openy_digital_signage_screen.module @@ -72,6 +72,7 @@ function openy_digital_signage_screen_preprocess_block(&$variables) { if (!isset($variables['attributes']['class'])) { $variables['attributes']['class'] = array(); } + $variables['attributes']['class'][] = 'block'; $variables['attributes']['class'][] = 'block-wrapper'; $variables['attributes']['class'][] = 'block-provider--' . $definition['provider']; $variables['attributes']['class'][] = 'block-plugin-id--' . $variables['plugin_id']; diff --git a/modules/openy_digital_signage_screen/scss/blocks/complete-schedule.scss b/modules/openy_digital_signage_screen/scss/blocks/complete-schedule.scss new file mode 100644 index 0000000..03ee2e9 --- /dev/null +++ b/modules/openy_digital_signage_screen/scss/blocks/complete-schedule.scss @@ -0,0 +1,123 @@ +.block-complete-schedule { + background-color: $grey; + font-family: $fontBook; + font-size: 1.625vw; + line-height: 1.2; +} + +.schedule-header { + background-color: $grey14; + color: $white; +} + +.schedule-table { + color: $white; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.schedule-table-ongoing, +.schedule-table-upcoming { + font-weight: 700; + font-size: 150%; + text-align: center; + text-transform: uppercase; +} + +.schedule-table-ongoing { + order: -2; +} + +.schedule-table-upcoming { + order: 1; +} + +.schedule-row { + display: flex; + position: relative; +} + +.schedule-row-class { + border-bottom: 0.5px solid rgba(0, 0, 0, 0.25); + border-top: 0.5px solid rgba(0, 0, 0, 0.25); + order: 2; + transition: height 500ms; +} + +.schedule-row-class-past { + height: 0; + border: none; +} + +.schedule-row-class-ongoing { + order: -1; +} + +.schedule-row-column { + display: block; + flex-basis: 0; + flex-grow: 2; + overflow: hidden; + padding: 0.1vw 0.8vw; + text-overflow: ellipsis; + white-space: nowrap; + z-index: 2; +} + +.trainer__substitute__icon {} + +.trainer__substituted {} + +.class-trainer { + text-align: center; +} + +.class-name { + flex-grow: 5; +} + +.class-time-frame { + flex-grow: 1; + padding-right: 0; + text-align: right; +} + +.class-time-frame-from {} + +.class-duration { + flex-grow: 1; +} + +span.class-duration { + &:before { + content: "("; + padding-right: 0.2vw; + } + + &:after { + content: ")"; + padding-left: 0.2vw; + } +} + +.class-room { + flex-grow: 3; +} + +.class-progress { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} + +.class-progress-bar { + background-color: rgba(0, 96, 175, 0.5); + height: 100%; + left: 0; + top: 0; + width: 0%; + z-index: 1; +} diff --git a/modules/openy_digital_signage_screen/scss/screen.scss b/modules/openy_digital_signage_screen/scss/screen.scss index e242bc8..0935024 100644 --- a/modules/openy_digital_signage_screen/scss/screen.scss +++ b/modules/openy_digital_signage_screen/scss/screen.scss @@ -23,3 +23,4 @@ @import "./blocks/system-message"; @import "./blocks/promotional"; @import "./blocks/class-ticker"; +@import "./blocks/complete-schedule"; diff --git a/modules/openy_digital_signage_screen/scss/ui/ui.scss b/modules/openy_digital_signage_screen/scss/ui/ui.scss index e2cb8d5..dd80161 100644 --- a/modules/openy_digital_signage_screen/scss/ui/ui.scss +++ b/modules/openy_digital_signage_screen/scss/ui/ui.scss @@ -1,5 +1,6 @@ body.node--type-screen-content, -body.page-node-type-screen-content { +body.page-node-type-screen-content, +body.ds-screen.ds-screen-full-size { padding-top: 0 !important; .openy-page-tabs { display: none; diff --git a/modules/openy_digital_signage_screen/src/Controller/OpenYScreenSchedule.php b/modules/openy_digital_signage_screen/src/Controller/OpenYScreenSchedule.php index 0172d59..f1c0299 100644 --- a/modules/openy_digital_signage_screen/src/Controller/OpenYScreenSchedule.php +++ b/modules/openy_digital_signage_screen/src/Controller/OpenYScreenSchedule.php @@ -176,7 +176,7 @@ public function editScheduleItem(OpenYScreenInterface $screen, OpenYScheduleItem } /** - * Gets a markup for showing the schedule item conten. + * Gets a markup for showing the schedule item content. * * @param OpenYScreenInterface $screen * The Screen entity. @@ -187,18 +187,19 @@ public function editScheduleItem(OpenYScreenInterface $screen, OpenYScheduleItem * Ajax response object. */ public function viewScheduleItem(Request $request, OpenYScreenInterface $screen, OpenYScheduleItemInterface $schedule_item) { - $screen_content = $schedule_item->content->entity; - if (empty($screen_content)) { + $screen_content = $schedule_item->content_ref->entity; + if (!$screen_content || empty($screen_content)) { // Return empty response. return new AjaxResponse(); } + $screen_content_entity_type = $screen_content->getEntityTypeId(); $from = $request->query->has('from') ? $request->query->get('from') : time(); $to = $request->query->has('to') ? $request->query->get('to') : time() + 8 * 3600; // Build an edit Schedule item form. - $src = Url::fromRoute('entity.node.canonical', [ - 'node' => $screen_content->id(), + $src = Url::fromRoute("entity.$screen_content_entity_type.canonical", [ + $screen_content_entity_type => $screen_content->id(), 'from' => $from, 'to' => $to, 'screen' => $screen->id(), @@ -239,6 +240,7 @@ public function viewScreenContent(OpenYScreenInterface $screen, NodeInterface $s '#type' => 'container', '#tag' => 'div', '#attributes' => [ + // TODO: double check this, maybe we need support here not only node. 'data-src' => Url::fromRoute('entity.node.canonical', ['node' => $screen_content->id()]) ->toString(), 'class' => ['frame-container'], diff --git a/modules/openy_digital_signage_screen/src/Entity/OpenYScreenViewBuilder.php b/modules/openy_digital_signage_screen/src/Entity/OpenYScreenViewBuilder.php index e38cba6..bfc07ca 100644 --- a/modules/openy_digital_signage_screen/src/Entity/OpenYScreenViewBuilder.php +++ b/modules/openy_digital_signage_screen/src/Entity/OpenYScreenViewBuilder.php @@ -2,28 +2,92 @@ namespace Drupal\openy_digital_signage_screen\Entity; -use Drupal\Core\Cache\RefinableCacheableDependencyInterface; -use Drupal\Core\Display\ContextAwareVariantInterface; +use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Field\FieldItemInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\PageCache\ResponsePolicy\KillSwitch; +use Drupal\openy_digital_signage_schedule\OpenYScheduleManager; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a view builder for OpenY Digital Signage Screen entities. */ -class OpenYScreenViewBuilder implements EntityViewBuilderInterface { +class OpenYScreenViewBuilder implements EntityHandlerInterface, EntityViewBuilderInterface { /** * Default timespan is a day. */ const TIMESPAN = 86400; + /** + * Kill Switch for page caching. + * + * @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch + */ + protected $killSwitch; + + /** + * Entity type manager service. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + + /** + * The Digital Signage Schedule manager service. + * + * @var \Drupal\openy_digital_signage_schedule\OpenYScheduleManager + */ + protected $scheduleManager; + + /** + * OpenYScreenViewBuilder constructor. + * + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * Entity type manager service. + * @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $kill_switch + * Kill Switch for page caching. + * @param \Drupal\openy_digital_signage_schedule\OpenYScheduleManager $schedule_manager + * The Digital Signage Schedule manager service. + */ + public function __construct(ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, KillSwitch $kill_switch, OpenYScheduleManager $schedule_manager) { + $this->killSwitch = $kill_switch; + $this->entityTypeManager = $entity_type_manager; + $this->moduleHandler = $module_handler; + $this->scheduleManager = $schedule_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('module_handler'), + $container->get('entity_type.manager'), + $container->get('page_cache_kill_switch'), + $container->get('openy_digital_signage_schedule.manager') + ); + } + /** * {@inheritdoc} */ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) { - $versions = \Drupal::moduleHandler()->invokeAll('ds_version'); + $versions = $this->moduleHandler->invokeAll('ds_version'); $version = md5(json_encode($versions)); $build = [ @@ -43,17 +107,17 @@ class="screen" ], ]; - \Drupal::service('page_cache_kill_switch')->trigger(); + $this->killSwitch->trigger(); if ($schedule = $entity->screen_schedule->entity) { - /** @var OpenYScheduleManager $schedule_manager */ - $schedule_manager = \Drupal::service('openy_digital_signage_schedule.manager'); - $schedule = $schedule_manager->getScreenUpcomingScreenContents($entity, self::TIMESPAN); - $render_controller = \Drupal::entityTypeManager()->getViewBuilder('node'); + $schedule = $this->scheduleManager->getScreenUpcomingScreenContents($entity, self::TIMESPAN); foreach ($schedule as $item) { if (!$screen_content = $item['content']) { continue; } + $entity_type_id = $item['content']->getEntityTypeId(); + $render_controller = $this->entityTypeManager->getViewBuilder($entity_type_id); + $period = &drupal_static('schedule_item_period'); $period = [ 'from' => $item['from'], @@ -61,7 +125,11 @@ class="screen" ]; $schedule_item_build = $render_controller->view($screen_content); $hash = md5(json_encode($schedule_item_build)); - $schedule_item_build['#prefix'] = '
    moduleHandler->alter('openy_digital_signage_screen_view', $build, $entity); + return $build; } diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_block_free_html.default.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_block_free_html.default.yml index c242d1d..91783b5 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_block_free_html.default.yml +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_block_free_html.default.yml @@ -3,9 +3,9 @@ status: true dependencies: config: - block_content.type.digital_signage_block_free_html + - entity_browser.browser.digital_signage_images_library - field.field.block_content.digital_signage_block_free_html.body - - field.field.block_content.digital_signage_block_free_html.field_ds_background_image - - entity_browser.browser.images_library + - field.field.block_content.digital_signage_block_free_html.field_ds_background_image_media module: - entity_browser - text @@ -23,17 +23,17 @@ content: placeholder: '' third_party_settings: { } region: content - field_ds_background_image: + field_ds_background_image_media: weight: 1 settings: - entity_browser: images_library + entity_browser: digital_signage_images_library field_widget_display: rendered_entity field_widget_edit: true field_widget_remove: true + open: true selection_mode: selection_append field_widget_display_settings: - view_mode: thumbnail_for_preview - open: false + view_mode: teaser third_party_settings: { } type: entity_browser_entity_reference region: content diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_promotional.default.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_promotional.default.yml index 0496f16..e772e84 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_promotional.default.yml +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_form_display.block_content.digital_signage_promotional.default.yml @@ -3,8 +3,9 @@ status: true dependencies: config: - block_content.type.digital_signage_promotional + - entity_browser.browser.digital_signage_images_library - field.field.block_content.digital_signage_promotional.field_ds_background_color - - field.field.block_content.digital_signage_promotional.field_ds_background_image + - field.field.block_content.digital_signage_promotional.field_ds_background_image_media - field.field.block_content.digital_signage_promotional.field_ds_background_position - field.field.block_content.digital_signage_promotional.field_ds_background_scheme - field.field.block_content.digital_signage_promotional.field_ds_background_size @@ -14,10 +15,9 @@ dependencies: - field.field.block_content.digital_signage_promotional.field_ds_message - field.field.block_content.digital_signage_promotional.field_ds_message_position - field.field.block_content.digital_signage_promotional.field_ds_subheading - - entity_browser.browser.images_library module: - - field_group - entity_browser + - field_group third_party_settings: field_group: group_ds_layout: @@ -36,7 +36,7 @@ third_party_settings: group_ds_background: children: - field_ds_background_color - - field_ds_background_image + - field_ds_background_image_media - field_ds_background_position - field_ds_background_size parent_name: '' @@ -74,22 +74,22 @@ content: third_party_settings: { } type: string_textfield region: content - field_ds_background_image: - weight: 6 + field_ds_background_image_media: + weight: 7 settings: - entity_browser: images_library + entity_browser: digital_signage_images_library field_widget_display: rendered_entity field_widget_edit: true field_widget_remove: true + open: true selection_mode: selection_append field_widget_display_settings: view_mode: thumbnail_for_preview - open: false third_party_settings: { } type: entity_browser_entity_reference region: content field_ds_background_position: - weight: 7 + weight: 8 settings: { } third_party_settings: { } type: options_select @@ -101,7 +101,7 @@ content: type: options_buttons region: content field_ds_background_size: - weight: 8 + weight: 9 settings: { } third_party_settings: { } type: options_buttons diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_block_free_html.default.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_block_free_html.default.yml index d81e3ed..8dc3582 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_block_free_html.default.yml +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_block_free_html.default.yml @@ -4,7 +4,7 @@ dependencies: config: - block_content.type.digital_signage_block_free_html - field.field.block_content.digital_signage_block_free_html.body - - field.field.block_content.digital_signage_block_free_html.field_ds_background_image + - field.field.block_content.digital_signage_block_free_html.field_ds_background_image_media module: - text id: block_content.digital_signage_block_free_html.default @@ -18,6 +18,7 @@ content: label: hidden settings: { } third_party_settings: { } + region: content field_ds_background_image: type: entity_reference_entity_view weight: 1 @@ -26,5 +27,14 @@ content: view_mode: default link: false third_party_settings: { } + region: content + field_ds_background_image_media: + weight: 2 + label: above + settings: + link: true + third_party_settings: { } + type: entity_reference_label + region: content hidden: langcode: true diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_promotional.default.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_promotional.default.yml index 4917b74..c221e07 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_promotional.default.yml +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/core.entity_view_display.block_content.digital_signage_promotional.default.yml @@ -4,7 +4,7 @@ dependencies: config: - block_content.type.digital_signage_promotional - field.field.block_content.digital_signage_promotional.field_ds_background_color - - field.field.block_content.digital_signage_promotional.field_ds_background_image + - field.field.block_content.digital_signage_promotional.field_ds_background_image_media - field.field.block_content.digital_signage_promotional.field_ds_background_position - field.field.block_content.digital_signage_promotional.field_ds_background_scheme - field.field.block_content.digital_signage_promotional.field_ds_background_size @@ -30,6 +30,14 @@ content: third_party_settings: { } type: image region: content + field_ds_background_image_media: + weight: 4 + label: above + settings: + link: true + third_party_settings: { } + type: entity_reference_label + region: content field_ds_headline: weight: 1 label: hidden diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.field.block_content.digital_signage_block_free_html.field_ds_background_image_media.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.field.block_content.digital_signage_block_free_html.field_ds_background_image_media.yml new file mode 100644 index 0000000..ec0d398 --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.field.block_content.digital_signage_block_free_html.field_ds_background_image_media.yml @@ -0,0 +1,27 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.digital_signage_block_free_html + - field.storage.block_content.field_ds_background_image_media + - media_entity.bundle.image +id: block_content.digital_signage_block_free_html.field_ds_background_image_media +field_name: field_ds_background_image_media +entity_type: block_content +bundle: digital_signage_block_free_html +label: 'Background image' +description: 'This image is used as a background for the content on the screen.' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + handler: 'default:media' + handler_settings: + target_bundles: + image: image + sort: + field: _none + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.field.block_content.digital_signage_promotional.field_ds_background_image_media.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.field.block_content.digital_signage_promotional.field_ds_background_image_media.yml new file mode 100644 index 0000000..8da6b48 --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.field.block_content.digital_signage_promotional.field_ds_background_image_media.yml @@ -0,0 +1,27 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.digital_signage_promotional + - field.storage.block_content.field_ds_background_image_media + - media_entity.bundle.image +id: block_content.digital_signage_promotional.field_ds_background_image_media +field_name: field_ds_background_image_media +entity_type: block_content +bundle: digital_signage_promotional +label: 'Background image' +description: 'This image is used as a background for the content on the screen.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:media' + handler_settings: + target_bundles: + image: image + sort: + field: _none + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.storage.block_content.field_ds_background_image_media.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.storage.block_content.field_ds_background_image_media.yml new file mode 100644 index 0000000..52793af --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/config/install/field.storage.block_content.field_ds_background_image_media.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - block_content + - media_entity +id: block_content.field_ds_background_image_media +field_name: field_ds_background_image_media +entity_type: block_content +type: entity_reference +settings: + target_type: media +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/css/complete-schedule.css b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/css/complete-schedule.css new file mode 100644 index 0000000..307abaa --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/css/complete-schedule.css @@ -0,0 +1,120 @@ +.block-complete-schedule { + font-size: 1.625vw; + line-height: 1.2; + font-family: "Cachet W01 Book", Verdana, sans-serif; + background-color: #636466; +} + +.schedule-header { + background-color: #333333; + color: #ffffff; +} + +.schedule-table { + color: #ffffff; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.schedule-table-ongoing, .schedule-table-upcoming { + text-align: center; + text-transform: uppercase; + font-weight: 700; + font-size: 150%; +} + +.schedule-table-ongoing { + order: -2; +} + +.schedule-table-upcoming { + order: 1; +} + +.schedule-row { + display: flex; + position: relative; +} + +.schedule-row-class { + transition: height 500ms; + order: 2; + border-top: 0.5px solid rgba(0, 0, 0, 0.25); + border-bottom: 0.5px solid rgba(0, 0, 0, 0.25); +} +.schedule-row-class-past { + height: 0; + border: none; +} + +.schedule-row-class-ongoing { + order: -1; +} + +.schedule-row-column { + display: block; + flex-basis: 0; + flex-grow: 2; + padding: 0.1vw 0.8vw; + z-index: 2; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.class-trainer { + text-align: center; +} + +.trainer__substitute__icon {} + +.trainer__substituted {} + +.class-name { + flex-grow: 5; +} + +.class-time-frame { + flex-grow: 1; + text-align: right; + padding-right: 0; +} + +.class-time-frame-from {} + +.class-duration { + flex-grow: 1; +} + +span.class-duration:before { + content: "("; + padding-right: 0.2vw; +} + +span.class-duration:after { + content: ")"; + padding-left: 0.2vw; +} + +.class-room { + flex-grow: 3; +} + +.class-progress { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.class-progress-bar { + top: 0; + left: 0; + width: 0%; + /*background-color: #0060af;*/ + background-color: rgba(0, 96, 175, 0.5); + height: 100%; + z-index: 1; +} diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/class-current.js b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/class-current.js index 0ca9479..cf03fc7 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/class-current.js +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/class-current.js @@ -337,10 +337,13 @@ var countdown_is_visible = parseFloat($('.class-time-countdown', activeClass).css('opacity')); var height_percent = countdown_is_visible === 0 ? 31 : 27; var size = parseInt($('.class-name', activeClass).css('font-size').slice(0, -2)); + var original_size = size; + var transition = $('.class-name', activeClass).css('transition'); var class_height = activeClass.height(); var class_name_height = $('.class-name', activeClass).height(); var percent = Math.round((class_name_height * 100) / class_height); var i = 0; + $('.class-name', activeClass).css('transition', 'none'); do { size -=1; $('.class-name', activeClass).css('font-size', size); @@ -349,6 +352,10 @@ i++; } while (percent > height_percent && i < 30); + $('.class-name', activeClass) + .css('font-size', original_size) + .css('transition', transition) + .css('font-size', size); }; return this; diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/complete-schedule.js b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/complete-schedule.js new file mode 100644 index 0000000..4c3b1a1 --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/js/complete-schedule.js @@ -0,0 +1,170 @@ +/** + * @file + * Block behaviors. + */ +(function ($, window, Drupal) { + + 'use strict'; + + /** + * Block current class storage. + */ + Drupal.openyDigitalSignageBlocks.currentClass = Drupal.openyDigitalSignageBlocks.currentClass || {}; + + /** + * Static bar specific behaviour. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the block. + */ + Drupal.behaviors.openyDigitalSignageBlockClassCurrent = { + attach: function (context, settings) { + $('.block-complete-schedule', context).once().each(function () { + if (!(Drupal.openyDigitalSignageBlocks.completeSchedule instanceof OpenYDigitalSignageBlockCompleteSchedule)) { + Drupal.openyDigitalSignageBlocks.completeSchedule = new OpenYDigitalSignageBlockCompleteSchedule(this); + } + Drupal.openyDigitalSignageBlocks.completeSchedule.deactivate(); + Drupal.openyDigitalSignageBlocks.completeSchedule.updateContext(this); + Drupal.openyDigitalSignageBlocks.completeSchedule.init(); + }); + } + }; + + /** + * Block current class handler. + * + * @param context + * Block. + * + * @returns {OpenYDigitalSignageBlockCompleteSchedule} + */ + function OpenYDigitalSignageBlockCompleteSchedule(context) { + var self = this; + this.context = context; + this.activated = 0; + + // General loop – basically swaps classes. + this.loop = function () { + + }; + + // Fast loop - basically updates the awaiting class. + this.fastloop = function () { + self.updateProgressBars(); + }; + + // Formats time. + this.formatTime = function (seconds) { + var fHours, fMinutes, fSeconds, separator; + separator = Math.floor(seconds) % 2 ? ':' : ':'; + if (seconds < 3600) { + fMinutes = Math.floor(seconds / 60); + fSeconds = Math.floor(seconds - fMinutes * 60); + if (fSeconds < 10) fSeconds = '0' + fSeconds; + if (fMinutes < 10) fMinutes = '0' + fMinutes; + + return { + suffix: seconds > 59 ? 'minutes' : 'seconds', + string: fMinutes + separator + fSeconds + }; + } + + fHours = Math.floor(seconds / 3600); + fMinutes = Math.floor((seconds - fHours * 3600) / 60); + if (fMinutes < 10) fMinutes = '0' + fMinutes; + + return { + suffix: 'hours', + string: fHours + separator + fMinutes + }; + }; + + // Updates progress bar of the current class. + this.updateProgressBars = function () { + var $classes = $('.schedule-row-class', self.context); + $classes.each(function () { + let $class = $(this); + let offset = self.getTimeOffset(); + let from = $class.data('from'); + let to = $class.data('to'); + let duration = to - from; + let progress = 100 * (offset - from) / duration; + let past = offset > (to + 5); + progress = Math.max(0, Math.min(progress, 100)); + $(this) + .find('.class-progress-bar') + .css('width', progress + '%') + if (offset >= from && offset <= to) { + $(this).addClass('schedule-row-class-ongoing'); + } + if (past) { + $(this).addClass('schedule-row-class-past'); + } + }); + }; + + /** + * Returns current time. + */ + this.getTimeOffset = function () { + return window.tm.getTime(); + }; + + /** + * Activates the block. + */ + this.activate = function () { + self.fastloop(); + self.timer = setInterval(self.loop, 5000); + self.fasttimer = setInterval(self.fastloop, 1000 / window.tm.speed); + self.activated = self.getTimeOffset(); + }; + + /** + * Deactivates the block. + */ + this.deactivate = function () { + clearInterval(self.timer); + clearInterval(self.fasttimer); + self.activated = 0; + }; + + /** + * Initialize block. + */ + this.init = function () { + self.blockObject = ObjectsManager.getObject(self.context); + self.blockObject.activate = self.activate; + self.blockObject.deactivate = self.deactivate; + if (self.blockObject.isActive() || $(self.context).parents('.screen').length === 0) { + self.activate(); + } + }; + + /** + * Check is block active and initialized or not. + * + * @returns {boolean} + * Status. + */ + this.isActive = function () { + return self.activated !== 0; + }; + + /** + * Update class context. + * + * @param context + * Block. + */ + this.updateContext = function (context) { + this.context = context; + self = this; + }; + + return this; + } + +})(jQuery, window, Drupal); diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.info.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.info.yml index 131065c..96abb11 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.info.yml +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.info.yml @@ -6,3 +6,4 @@ package: "OpenY: Digital Signage" dependencies: - openy_digital_signage_screen_content - openy_digital_signage_room + diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.install b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.install index a2acb93..5ff6c9a 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.install +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.install @@ -102,6 +102,41 @@ function openy_digital_signage_blocks_install() { $config->save(); } +/** + * Replace the 'field_ds_background_image' file field with Media. + */ +function openy_digital_signage_blocks_update_8001(&$sandbox) { + // @todo migrate files from the old field to the new field. + /* @var $entityFieldManager Drupal\Core\Entity\EntityFieldManager */ + $entityFieldManager = Drupal::service('entity_field.manager'); + + $ds_bundles = [ + OPENY_DS_BLOCK_PROMOTIONAL, + OPENY_DS_BLOCK_HTML_BUNDLE, + ]; + + foreach ($ds_bundles as $bundle) { + $fields = $entityFieldManager->getFieldDefinitions('block_content', $bundle); + + if (isset($fields['field_ds_background_image'])) { + $fields['field_ds_background_image']->delete(); + } + } + + $config_dir = drupal_get_path('module', 'openy_digital_signage_blocks') . '/config/install'; + $config_importer = \Drupal::service('openy_upgrade_tool.importer'); + $config_importer->setDirectory($config_dir); + $config_importer->importConfigs([ + 'core.entity_form_display.block_content.digital_signage_block_free_html.default', + 'core.entity_form_display.block_content.digital_signage_promotional.default', + 'core.entity_view_display.block_content.digital_signage_block_free_html.default', + 'core.entity_view_display.block_content.digital_signage_promotional.default', + 'field.field.block_content.digital_signage_block_free_html.field_ds_background_image_media', + 'field.field.block_content.digital_signage_promotional.field_ds_background_image_media', + 'field.storage.block_content.field_ds_background_image_media', + ]); +} + /** * Implements hook_uninstall(). */ diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.libraries.yml b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.libraries.yml index b434087..349a213 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.libraries.yml +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.libraries.yml @@ -64,3 +64,11 @@ promotional_layout_preview: css: component: css/promo-block-form-preview.css: {} + +complete_schedule: + version: VERSION + js: + js/complete-schedule.js: {} + css: + component: + css/complete-schedule.css: {} diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.module b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.module index c1d5203..fcd1713 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.module +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/openy_digital_signage_blocks.module @@ -66,6 +66,14 @@ function openy_digital_signage_blocks_theme($existing, $type, $theme, $path) { 'wrapper_attributes' => NULL, ], ], + 'openy_digital_signage_blocks_complete_schedule' => [ + 'template' => 'openy-digital-signage-blocks-complete-schedule', + 'variables' => [ + 'room' => '', + 'classes' => NULL, + 'wrapper_attributes' => NULL, + ], + ], ]; } @@ -114,8 +122,8 @@ function openy_digital_signage_blocks_preprocess_block(&$variables) { $attributes->addClass('block-promotional-message-position-' . $block->field_ds_message_position->value); $attributes->addClass('block-promotional-layout-' . $block->field_ds_layout->value); $background_styles = ['background-color: ' . $block->field_ds_background_color->value . ';']; - if ($background_image = $block->field_ds_background_image->entity) { - $background_styles[] = 'background-image:url(' . file_create_url($background_image->getFileUri()) . ');'; + if ($url = openy_digital_signage_blocks_get_background_url($block)) { + $background_styles[] = 'background-image:url(' . $url . ');'; } $attributes->setAttribute('style', $background_styles); $variables['wrapper_attributes'] = $attributes; @@ -123,12 +131,10 @@ function openy_digital_signage_blocks_preprocess_block(&$variables) { case OPENY_DS_BLOCK_HTML_BUNDLE: // Do not display image. - $variables['content']['field_ds_background_image']['#access'] = FALSE; + $variables['content']['field_ds_background_image_media']['#access'] = FALSE; $variables['background_image'] = ''; - if (!empty($variables['content']['field_ds_background_image'][0]['#file'])) { - /* @var \Drupal\file_entity\Entity\FileEntity $file */ - $file = $variables['content']['field_ds_background_image'][0]['#file']; - $variables['background_image'] = file_create_url($file->getFileUri()); + if ($url = openy_digital_signage_blocks_get_background_url($block)) { + $variables['background_image'] = $url; } break; } @@ -158,10 +164,10 @@ function openy_digital_signage_blocks_form_alter(&$form, FormStateInterface $for '#type' => 'fieldset', '#title' => t('Background Image settings'), '#collapsible' => TRUE, - '#weight' => $form['field_ds_background_image']['#weight'], + '#weight' => $form['field_ds_background_image_media']['#weight'], ]; - $form['background_image_settings']['field_ds_background_image'] = $form['field_ds_background_image']; - unset($form['field_ds_background_image']); + $form['background_image_settings']['field_ds_background_image_media'] = $form['field_ds_background_image_media']; + unset($form['field_ds_background_image_media']); $form['background_image_settings']['field_ds_background_position'] = $form['field_ds_background_position']; unset($form['field_ds_background_position']); $form['background_image_settings']['field_ds_background_size'] = $form['field_ds_background_size']; @@ -267,3 +273,30 @@ function openy_digital_signage_blocks_hide_text_format_switcher($form_element, F return $form_element; } + +/** + * Gets background file URL from media of content block. + * + * @param \Drupal\block_content\Entity\BlockContent $block + * DS content block. + * + * @return string|null + * URL or NULL. + */ +function openy_digital_signage_blocks_get_background_url(BlockContent $block) { + $ds_bundles = [ + OPENY_DS_BLOCK_PROMOTIONAL, + OPENY_DS_BLOCK_HTML_BUNDLE, + ]; + + if (!in_array($block->bundle(), $ds_bundles)) { + return NULL; + } + + if ($background_media = $block->field_ds_background_image_media->entity) { + $file = $background_media->field_media_image->entity; + return file_create_url($file->getFileUri()); + } + + return NULL; +} diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassCurrent.php b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassCurrent.php index 6829b10..8e31140 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassCurrent.php +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassCurrent.php @@ -6,7 +6,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Template\Attribute; -use Drupal\openy_digital_signage_classes_schedule\OpenYClassesScheduleManagerInterface; use Drupal\openy_digital_signage_room\OpenYRoomManagerInterface; use Drupal\openy_digital_signage_screen\OpenYScreenManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -26,8 +25,6 @@ class OpenYDigitalSignageBlockClassCurrent extends BlockBase implements Containe /** * The Classes Schedule Manager. - * - * @var OpenYClassesScheduleManagerInterface */ protected $scheduleManager; @@ -45,6 +42,13 @@ class OpenYDigitalSignageBlockClassCurrent extends BlockBase implements Containe */ protected $roomManager; + /** + * The container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + /** * OpenYDigitalSignageBlockClassCurrent constructor. * @@ -54,17 +58,19 @@ class OpenYDigitalSignageBlockClassCurrent extends BlockBase implements Containe * The plugin id. * @param mixed $plugin_definition * The plugin definition. - * @param \Drupal\openy_digital_signage_classes_schedule\OpenYClassesScheduleManagerInterface $schedule_manager - * The Open Y DS Classes Schedule Manager. + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. * @param \Drupal\openy_digital_signage_screen\OpenYScreenManagerInterface $screen_manager * The Open Y DS Screen Manager. + * @param \Drupal\openy_digital_signage_room\OpenYRoomManagerInterface $room_manager + * The Open Y DS Room Manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, OpenYClassesScheduleManagerInterface $schedule_manager, OpenYScreenManagerInterface $screen_manager, OpenYRoomManagerInterface $room_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, ContainerInterface $container, OpenYScreenManagerInterface $screen_manager, OpenYRoomManagerInterface $room_manager) { // Call parent construct method. parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->scheduleManager = $schedule_manager; $this->screenManager = $screen_manager; $this->roomManager = $room_manager; + $this->container = $container; } /** @@ -75,7 +81,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('openy_digital_signage_classes_schedule.manager'), + $container, $container->get('openy_digital_signage_screen.manager'), $container->get('openy_digital_signage_room.manager') ); @@ -87,6 +93,8 @@ public static function create(ContainerInterface $container, array $configuratio public function defaultConfiguration() { return [ 'room' => 0, + 'source' => 'pef', + 'category' => [], ]; } @@ -94,6 +102,13 @@ public function defaultConfiguration() { * {@inheritdoc} */ public function blockForm($form, FormStateInterface $form_state) { + $form['source'] = [ + '#type' => 'select', + '#title' => $this->t('Data source'), + '#description' => $this->t('Specify where the class schedule comes from'), + '#default_value' => $this->getDataSource(), + '#options' => $this->getSourceOptions(), + ]; $form['room'] = [ '#type' => 'select', '#title' => $this->t('Room'), @@ -101,6 +116,27 @@ public function blockForm($form, FormStateInterface $form_state) { '#default_value' => $this->configuration['room'], '#options' => $this->roomManager->getAllRoomOptions(), ]; + $form['category'] = [ + '#type' => 'select', + '#multiple' => TRUE, + '#chosen' => TRUE, + '#title' => $this->t('Category'), + '#description' => $this->t('Additionally filter schedule by activity category'), + '#default_value' => $this->getCategories(), + '#options' => $this->getAllCategoryOptions(), + '#states' => [ + 'visible' => [ + '[name="settings[source]"' => ['value' => 'pef'], + ], + ], + ]; + + // Prevents the chosen dropdown from being cut off. + $form['styles'] = [ + '#type' => 'inline_template', + '#template' => "", + ]; + return $form; } @@ -108,7 +144,12 @@ public function blockForm($form, FormStateInterface $form_state) { * {@inheritdoc} */ public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['source'] = $form_state->getValue('source'); $this->configuration['room'] = $form_state->getValue('room'); + $this->configuration['category'] = []; + if ($this->configuration['source'] == 'pef') { + $this->configuration['category'] = array_filter($form_state->getValue('category')); + } } /** @@ -120,8 +161,21 @@ public function build() { $attributes->addClass('block-class-current'); $period = $this->getSchedulePeriod(); + + $classes = []; if ($room = $this->getRoom()) { - $classes = $this->scheduleManager->getClassesSchedule($period, $room); + if ($this->getDataSource() == 'pef') { + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_ds_pef_schedule.manager'); + $classes = $this->scheduleManager->getClassesSchedule($period, $this->scheduleManager->getNextDayAlways(), null, [$room], $this->getCategories()); + } + } + else { + if ($this->container->has('openy_digital_signage_classes_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_digital_signage_classes_schedule.manager'); + $classes = $this->scheduleManager->getClassesSchedule($period, $room, $this->getCategories()); + } + } } else { $classes = $this->getDummyClassesSchedule($period); @@ -159,6 +213,67 @@ private function getRoom() { return $screen_room ? $screen_room->id() : $this->configuration['room']; } + /** + * Retrieves data source. + * + * @return int|null + * The room id context. + */ + private function getDataSource() { + $data_source = $this->defaultConfiguration()['source']; + if (!empty($this->configuration['source'])) { + $data_source = $this->configuration['source']; + } + + return $data_source; + } + + /** + * Retrieves category configuration. + * + * @return array + */ + private function getCategories() { + $category = $this->defaultConfiguration()['category']; + if (!empty($this->configuration['category'])) { + $category = $this->configuration['category']; + } + + return $category; + } + + /** + * Returns available datasource options. + * + * @return array + */ + public function getSourceOptions() { + $options = []; + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $options['pef'] = $this->t('Program Event Framework'); + } + if ($this->container->has('openy_digital_signage_classes_schedule.manager')) { + $options['ds'] = $this->t('Open Y Digital Signage classes and session'); + } + + return $options; + } + + /** + * Retrieves all available category options. + * + * @return array + */ + public function getAllCategoryOptions() { + $categories = []; + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_ds_pef_schedule.manager'); + $categories = $this->scheduleManager->getAllCategories(); + } + + return $categories; + } + /** * Retrieve schedule period. * diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassTicker.php b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassTicker.php index 505a4ec..d857a1f 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassTicker.php +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockClassTicker.php @@ -6,7 +6,6 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Template\Attribute; -use Drupal\openy_digital_signage_classes_schedule\OpenYClassesScheduleManagerInterface; use Drupal\openy_digital_signage_room\OpenYRoomManagerInterface; use Drupal\openy_digital_signage_screen\Entity\OpenYScreenInterface; use Drupal\openy_digital_signage_screen\OpenYScreenManagerInterface; @@ -27,8 +26,6 @@ class OpenYDigitalSignageBlockClassTicker extends BlockBase implements Container /** * The Classes Schedule Manager. - * - * @var OpenYClassesScheduleManagerInterface */ protected $scheduleManager; @@ -46,6 +43,13 @@ class OpenYDigitalSignageBlockClassTicker extends BlockBase implements Container */ protected $roomManager; + /** + * The container. + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + /** * OpenYDigitalSignageBlockClassTicker constructor. * @@ -55,17 +59,19 @@ class OpenYDigitalSignageBlockClassTicker extends BlockBase implements Container * The plugin id. * @param mixed $plugin_definition * The plugin definition. - * @param \Drupal\openy_digital_signage_classes_schedule\OpenYClassesScheduleManagerInterface $schedule_manager - * The Open Y DS Classes Schedule Manager. + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * The container. * @param \Drupal\openy_digital_signage_screen\OpenYScreenManagerInterface $screen_manager * The Open Y DS Screen Manager. + * @param \Drupal\openy_digital_signage_room\OpenYRoomManagerInterface $room_manager + * The Open Y DS Room Manager. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, OpenYClassesScheduleManagerInterface $schedule_manager, OpenYScreenManagerInterface $screen_manager, OpenYRoomManagerInterface $room_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, ContainerInterface $container, OpenYScreenManagerInterface $screen_manager, OpenYRoomManagerInterface $room_manager) { // Call parent construct method. parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->scheduleManager = $schedule_manager; $this->screenManager = $screen_manager; $this->roomManager = $room_manager; + $this->container = $container; } /** @@ -76,7 +82,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, - $container->get('openy_digital_signage_classes_schedule.manager'), + $container, $container->get('openy_digital_signage_screen.manager'), $container->get('openy_digital_signage_room.manager') ); @@ -88,6 +94,8 @@ public static function create(ContainerInterface $container, array $configuratio public function defaultConfiguration() { return [ 'room' => 0, + 'source' => 'pef', + 'category' => [], ]; } @@ -95,6 +103,13 @@ public function defaultConfiguration() { * {@inheritdoc} */ public function blockForm($form, FormStateInterface $form_state) { + $form['source'] = [ + '#type' => 'select', + '#title' => $this->t('Data source'), + '#description' => $this->t('Specify where the class schedule comes from'), + '#default_value' => $this->getDataSource(), + '#options' => $this->getSourceOptions(), + ]; $form['room'] = [ '#type' => 'select', '#title' => $this->t('Room'), @@ -102,6 +117,27 @@ public function blockForm($form, FormStateInterface $form_state) { '#default_value' => $this->configuration['room'], '#options' => $this->roomManager->getAllRoomOptions(), ]; + $form['category'] = [ + '#type' => 'select', + '#multiple' => TRUE, + '#chosen' => TRUE, + '#title' => $this->t('Category'), + '#description' => $this->t('Additionally filter schedule by activity category'), + '#default_value' => $this->getCategories(), + '#options' => $this->getAllCategoryOptions(), + '#states' => [ + 'visible' => [ + '[name="settings[source]"' => ['value' => 'pef'], + ], + ], + ]; + + // Prevents the chosen dropdown from being cut off. + $form['styles'] = [ + '#type' => 'inline_template', + '#template' => "", + ]; + return $form; } @@ -109,7 +145,12 @@ public function blockForm($form, FormStateInterface $form_state) { * {@inheritdoc} */ public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['source'] = $form_state->getValue('source'); $this->configuration['room'] = $form_state->getValue('room'); + $this->configuration['category'] = []; + if ($this->configuration['source'] == 'pef') { + $this->configuration['category'] = array_filter($form_state->getValue('category')); + } } /** @@ -121,8 +162,21 @@ public function build() { $attributes->addClass('block-class-ticker'); $period = $this->getSchedulePeriod(); + + $classes = []; if ($room = $this->getRoom()) { - $classes = $this->scheduleManager->getClassesSchedule($period, $room); + if ($this->getDataSource() == 'pef') { + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_ds_pef_schedule.manager'); + $classes = $this->scheduleManager->getClassesSchedule($period, $this->scheduleManager->getNextDayAlways(), null, [$room], $this->getCategories()); + } + } + else { + if ($this->container->has('openy_digital_signage_classes_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_digital_signage_classes_schedule.manager'); + $classes = $this->scheduleManager->getClassesSchedule($period, $room, $this->getCategories()); + } + } } else { $classes = $this->getDummyClassesSchedule($period); @@ -153,11 +207,72 @@ public function build() { * The room id context. */ private function getRoom() { - $screen = $this->screenManager->getScreenContext(); - if ($screen && $screen->room->entity) { - return $screen->room->entity->id(); + if (!$screen = $this->screenManager->getScreenContext()) { + return $this->configuration['room']; } - return $this->configuration['room']; + $screen_room = $screen->room->entity; + return $screen_room ? $screen_room->id() : $this->configuration['room']; + } + + /** + * Retrieves data source. + * + * @return int|null + * The room id context. + */ + private function getDataSource() { + $data_source = $this->defaultConfiguration()['source']; + if (!empty($this->configuration['source'])) { + $data_source = $this->configuration['source']; + } + + return $data_source; + } + + /** + * Retrieves category configuration. + * + * @return array + */ + private function getCategories() { + $category = $this->defaultConfiguration()['category']; + if (!empty($this->configuration['category'])) { + $category = $this->configuration['category']; + } + + return $category; + } + + /** + * Returns available datasource options. + * + * @return array + */ + public function getSourceOptions() { + $options = []; + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $options['pef'] = $this->t('Program Event Framework'); + } + if ($this->container->has('openy_digital_signage_classes_schedule.manager')) { + $options['ds'] = $this->t('Open Y Digital Signage classes and session'); + } + + return $options; + } + + /** + * Retrieves all available category options. + * + * @return array + */ + public function getAllCategoryOptions() { + $categories = []; + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_ds_pef_schedule.manager'); + $categories = $this->scheduleManager->getAllCategories(); + } + + return $categories; } /** diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockCompleteSchedule.php b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockCompleteSchedule.php new file mode 100644 index 0000000..16caba7 --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/src/Plugin/Block/OpenYDigitalSignageBlockCompleteSchedule.php @@ -0,0 +1,338 @@ +screenManager = $screen_manager; + $this->roomManager = $room_manager; + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container, + $container->get('openy_digital_signage_screen.manager'), + $container->get('openy_digital_signage_room.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'room' => 0, + 'source' => 'pef', + 'category' => [], + ]; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $form['source'] = [ + '#type' => 'select', + '#title' => $this->t('Data source'), + '#description' => $this->t('Specify where the class schedule comes from'), + '#default_value' => $this->getDataSource(), + '#options' => $this->getSourceOptions(), + ]; + $form['room'] = [ + '#type' => 'select', + '#multiple' => TRUE, + '#title' => $this->t('Room'), + '#description' => $this->t('The block is shown in context of the screen. If the screen has no room/studio specified, this value is used'), + '#default_value' => $this->configuration['room'], + '#options' => $this->roomManager->getAllRoomOptions(), + ]; + $form['category'] = [ + '#type' => 'select', + '#multiple' => TRUE, + '#chosen' => TRUE, + '#title' => $this->t('Category'), + '#description' => $this->t('Additionally filter schedule by activity category'), + '#default_value' => $this->getCategories(), + '#options' => $this->getAllCategoryOptions(), + '#states' => [ + 'visible' => [ + '[name="settings[source]"' => ['value' => 'pef'], + ], + ], + ]; + + // Prevents the chosen dropdown from being cut off. + $form['styles'] = [ + '#type' => 'inline_template', + '#template' => "", + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['source'] = $form_state->getValue('source'); + $this->configuration['room'] = $form_state->getValue('room'); + $this->configuration['category'] = []; + if ($this->configuration['source'] == 'pef') { + $this->configuration['category'] = array_filter($form_state->getValue('category')); + } + } + + /** + * {@inheritdoc} + */ + public function build() { + $attributes = new Attribute(); + $attributes->addClass('block'); + $attributes->addClass('block-class-current'); + + $period = $this->getSchedulePeriod(); + + $classes = []; + if ($this->getDataSource() == 'pef') { + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_ds_pef_schedule.manager'); + $classes = $this->scheduleManager->getClassesSchedule($period, $this->scheduleManager->getNextDayIfEmpty(), $this->getLocation(), $this->getRooms(), $this->getCategories()); } + } + else { + if ($this->container->has('openy_digital_signage_classes_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_digital_signage_classes_schedule.manager'); + $classes = $this->scheduleManager->getClassesSchedule($period, $this->getRooms(), $this->getCategories()); + } + } + + $build = [ + '#theme' => 'openy_digital_signage_blocks_complete_schedule', + '#attached' => [ + 'library' => [ + 'openy_digital_signage_blocks/complete_schedule', + ], + ], + '#cache' => [ + 'max-age' => 0, + ], + '#room' => $this->configuration['room'], + '#classes' => $classes, + '#wrapper_attributes' => $attributes, + ]; + + return $build; + } + + /** + * Retrieves room. + * + * @return int|null + * The room id context. + */ + private function getRooms() { + return $this->configuration['room']; + } + + private function getLocation() { + if (!$screen = $this->screenManager->getScreenContext()) { + return 0; + } + $screen_location = $screen->field_screen_location->entity; + return $screen_location ? $screen_location->id() : null; + } + + /** + * Retrieves data source. + * + * @return int|null + * The room id context. + */ + private function getDataSource() { + $data_source = $this->defaultConfiguration()['source']; + if (!empty($this->configuration['source'])) { + $data_source = $this->configuration['source']; + } + + return $data_source; + } + + /** + * Retrieves category configuration. + * + * @return array + */ + private function getCategories() { + $category = $this->defaultConfiguration()['category']; + if (!empty($this->configuration['category'])) { + $category = $this->configuration['category']; + } + + return $category; + } + + /** + * Returns available datasource options. + * + * @return array + */ + public function getSourceOptions() { + $options = []; + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $options['pef'] = $this->t('Program Event Framework'); + } + if ($this->container->has('openy_digital_signage_classes_schedule.manager')) { + $options['ds'] = $this->t('Open Y Digital Signage classes and session'); + } + + return $options; + } + + /** + * Retrieves all available category options. + * + * @return array + */ + public function getAllCategoryOptions() { + $categories = []; + if ($this->container->has('openy_ds_pef_schedule.manager')) { + $this->scheduleManager = $this->container->get('openy_ds_pef_schedule.manager'); + $categories = $this->scheduleManager->getAllCategories(); + } + + return $categories; + } + + /** + * Retrieve schedule period. + * + * @return array + * The schedule period. + */ + private function getSchedulePeriod() { + $period = &drupal_static('schedule_item_period', NULL); + + if (isset($period)) { + return $period; + } + + if (isset($_GET['from'], $_GET['to'])) { + return [ + 'from' => $_GET['from'], + 'to' => $_GET['to'], + ]; + + } + return [ + 'from' => time(), + 'to' => time() + $this::DEFAULT_PERIOD_LENGTH, + ]; + } + + /** + * Generates dummy class schedule. + * + * @param array $period + * Period of time the schedule to be generated. + * + * @return array + * The generated schedule. + */ + private function getDummyClassesSchedule($period) { + $classes = []; + $time = $period['from']; + $cnt = 19; + $duration = ceil(($period['to'] - $period['from']) / ($cnt)); + $break_duration = intval($duration * 4 / 13); + $duration -= $break_duration; + for ($i = 0; $i < $cnt; $i++) { + $from = $time; + $to = $from + $duration; + $time = $to + $break_duration; + $classes[] = [ + 'from' => $from, + 'to' => $to, + 'duration_raw' => $duration, + 'duration' => $duration . 'm', + 'trainer' => 'Nichole C.', + 'substitute_trainer' => rand(0, 10) < 5 ? 'Substitute T.' : '', + 'name' => 'OULA® Dance Fitness', + 'from_formatted' => date('g:ia', $from), + 'to_formatted' => date('g:ia', $to), + ]; + } + + return $classes; + } + +} diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-block-free-html.html.twig b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-block-free-html.html.twig index b4b5f42..2f11f32 100755 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-block-free-html.html.twig +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-block-free-html.html.twig @@ -27,7 +27,7 @@ * @ingroup themeable */ #} - +
    {{ title_prefix }} diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-promotional.html.twig b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-promotional.html.twig index 127c8a8..6f52a41 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-promotional.html.twig +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/block--digital-signage-promotional.html.twig @@ -28,7 +28,7 @@ */ #} -
    +
    {% block content %}
    @@ -45,7 +45,7 @@ {% if content.field_ds_message[0]['#context'].value %}
    - {{ content|without('field_ds_background_image', 'field_ds_headline', 'field_ds_subheading') }} + {{ content|without('field_ds_background_image_media', 'field_ds_headline', 'field_ds_subheading') }}
    {% endif %} diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-class-current.html.twig b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-class-current.html.twig index e0aadae..0399363 100644 --- a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-class-current.html.twig +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-class-current.html.twig @@ -18,7 +18,7 @@
    -
    {{ "Coming next"|t }}
    +
    {{ "Up next"|t }}
    diff --git a/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-complete-schedule.html.twig b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-complete-schedule.html.twig new file mode 100644 index 0000000..22aec89 --- /dev/null +++ b/modules/openy_digital_signage_screen_content/modules/openy_digital_signage_blocks/templates/openy-digital-signage-blocks-complete-schedule.html.twig @@ -0,0 +1,64 @@ +{# +/** + * @file + * Default theme implementation to display a System Message block. + * + * Available variables: + * - wrapper_attributes: array of HTML attributes, intended to be added to the main + * container tag of this template. + * - classes: A list of classes to identify position and styles of the block. + * - room: The room. + * - classes: The array of classes. + * + * @ingroup themeable + */ +#} + +
    +
    +
    +
    + {{ 'Time'|t }} +
    +
    + {{ 'Duration'|t }} +
    +
    + {{ 'Class name'|t }} +
    +
    + {{ 'Trainer'|t }} +
    +
    + {{ 'Room'|t }} +
    +
    +
    +
    +
    Ongoing
    +
    Upcoming
    + {% for class in classes %} +
    +
    + {{ class.from_formatted }} +
    +
    + {{ class.duration }} +
    +
    {{ class.name|raw }}
    +
    + {% if class.substitute_trainer %} + + {{ class.trainer }} + {% else %} + {{ class.trainer }} + {% endif %} +
    +
    {{ class.room|raw }}
    +
    +
    +
    +
    + {% endfor %} +
    +
    diff --git a/modules/openy_digital_signage_screen_content/src/Controller/OpenYDSPanelsIPEPageController.php b/modules/openy_digital_signage_screen_content/src/Controller/OpenYDSPanelsIPEPageController.php index 9ae4bd1..53df623 100644 --- a/modules/openy_digital_signage_screen_content/src/Controller/OpenYDSPanelsIPEPageController.php +++ b/modules/openy_digital_signage_screen_content/src/Controller/OpenYDSPanelsIPEPageController.php @@ -20,11 +20,19 @@ class OpenYDSPanelsIPEPageController extends PanelsIPEPageController { * Status. */ public function isEntityScreenContent($panels_storage_id) { + if (strrpos($panels_storage_id, 'screen_content')) { + return TRUE; + } + $storage_keys = explode(':', $panels_storage_id); $entity_manager = \Drupal::entityTypeManager(); $entity = $entity_manager->getStorage('node') ->load($storage_keys[1]); - return $entity->bundle() == 'screen_content'; + if ($entity) { + return $entity->bundle() == 'screen_content'; + } + + return FALSE; } /** @@ -94,6 +102,11 @@ public function getBlockPluginsData($panels_storage_type, $panels_storage_id) { } $panels_display = $this->loadPanelsDisplay($panels_storage_type, $panels_storage_id); + $contexts = $this->tempStore->get($panels_display->getStorageId() . '-context'); + if ($contexts) { + $panels_display->setContexts($contexts); + } + // Get block plugin definitions from the server. $definitions = $this->blockManager->getDefinitionsForContexts($panels_display->getContexts()); diff --git a/modules/openy_ds_media_library/config/install/core.entity_form_display.media.image.digital_signage_media.yml b/modules/openy_ds_media_library/config/install/core.entity_form_display.media.image.digital_signage_media.yml new file mode 100644 index 0000000..2ae5399 --- /dev/null +++ b/modules/openy_ds_media_library/config/install/core.entity_form_display.media.image.digital_signage_media.yml @@ -0,0 +1,62 @@ +uuid: ec0a1b2d-26a8-480f-8a9c-33051d5bc358 +langcode: en +status: true +dependencies: + config: + - core.entity_form_mode.media.digital_signage_media + - field.field.media.image.field_media_caption + - field.field.media.image.field_media_image + - field.field.media.image.field_media_in_library + - field.field.media.image.field_media_tags + - image.style.thumbnail_focal_point + - media_entity.bundle.image + module: + - focal_point +_core: + default_config_hash: rRJJPqkvU7b4dP6du23LZDutvvFh28WOAdHCbbf0xhI +id: media.image.digital_signage_media +targetEntityType: media +bundle: image +mode: digital_signage_media +content: + field_media_image: + weight: 3 + settings: + preview_image_style: thumbnail_focal_point + progress_indicator: throbber + preview_link: true + offsets: '50,50' + third_party_settings: { } + type: image_focal_point + region: content + field_media_tags: + weight: 1 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } + type: entity_reference_autocomplete_tags + region: content + langcode: + type: language_select + weight: 2 + region: content + settings: + include_locked: true + third_party_settings: { } + name: + type: string_textfield + weight: 0 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + region: content +hidden: + created: true + field_media_caption: true + field_media_in_library: true + moderation_state: true + path: true + uid: true diff --git a/modules/openy_ds_media_library/config/install/core.entity_form_mode.media.digital_signage_media.yml b/modules/openy_ds_media_library/config/install/core.entity_form_mode.media.digital_signage_media.yml new file mode 100644 index 0000000..9f43251 --- /dev/null +++ b/modules/openy_ds_media_library/config/install/core.entity_form_mode.media.digital_signage_media.yml @@ -0,0 +1,10 @@ +uuid: fbd16cc9-7326-42c0-82e6-8fdeaecf6d0d +langcode: en +status: true +dependencies: + module: + - media_entity +id: media.digital_signage_media +label: 'Digital signage media' +targetEntityType: media +cache: true diff --git a/modules/openy_ds_media_library/config/install/entity_browser.browser.digital_signage_images_library.yml b/modules/openy_ds_media_library/config/install/entity_browser.browser.digital_signage_images_library.yml new file mode 100644 index 0000000..a390622 --- /dev/null +++ b/modules/openy_ds_media_library/config/install/entity_browser.browser.digital_signage_images_library.yml @@ -0,0 +1,55 @@ +uuid: 11bf921a-ef5c-459e-a48c-b62c66992692 +langcode: en +status: true +dependencies: + config: + - media_entity.bundle.image + - views.view.digital_signage_images_library + module: + - dropzonejs_eb_widget + - media_entity + - views +_core: + default_config_hash: _MNAphXAG4qCL7nCsXV_1Gb2RCao0OiXgG-bPo76P0A +name: digital_signage_images_library +label: 'Digital Signage Images Library' +display: modal +display_configuration: + width: '1166' + height: '600' + link_text: 'Select images' + auto_open: false +selection_display: no_display +selection_display_configuration: { } +widget_selector: tabs +widget_selector_configuration: { } +widgets: + 066bf7d9-6d8d-46cd-ac5c-a0944058e8c4: + settings: + view: digital_signage_images_library + view_display: images_library + submit_text: 'Select images' + auto_select: false + uuid: 066bf7d9-6d8d-46cd-ac5c-a0944058e8c4 + weight: -8 + label: 'All Images' + id: view + 89e6e16d-6b22-45f4-b91a-f37edaa85f64: + settings: + form_mode: digital_signage_media + media_entity_bundle: image + upload_location: 'public://digital_signage/[date:custom:Y]-[date:custom:m]' + dropzone_description: 'Drop files here to upload them' + max_filesize: 10M + extensions: 'png jpg jpeg gif svg' + clientside_resize: false + resize_width: 0 + resize_height: 0 + resize_quality: !!float 1 + resize_method: contain + thumbnail_method: contain + submit_text: 'Select images' + uuid: 89e6e16d-6b22-45f4-b91a-f37edaa85f64 + weight: -10 + label: 'Upload images' + id: dropzonejs_media_entity_inline_entity_form diff --git a/modules/openy_ds_media_library/config/install/views.view.digital_signage_images_library.yml b/modules/openy_ds_media_library/config/install/views.view.digital_signage_images_library.yml new file mode 100644 index 0000000..d895ab9 --- /dev/null +++ b/modules/openy_ds_media_library/config/install/views.view.digital_signage_images_library.yml @@ -0,0 +1,448 @@ +uuid: 08326bcf-62b6-493e-b131-1738beb120a1 +langcode: en +status: true +dependencies: + config: + - image.style.browser_thumbnail + - media_entity.bundle.image + module: + - entity_browser + - image + - media_entity + - user +_core: + default_config_hash: L82HYSEP88PlPW6rB_VIk8v2kK8RZ6tw4XYfQ8ujPxM +id: digital_signage_images_library +label: 'Digital Signage Images Library' +module: views +description: 'Provides images library for images_library entity browser.' +tag: '' +base_table: media_field_data +base_field: mid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access media overview' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 12 + offset: 0 + id: 0 + total_pages: null + tags: + previous: ‹‹ + next: ›› + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + style: + type: grid + options: + grouping: { } + columns: 4 + automatic_width: true + alignment: horizontal + col_class_default: true + col_class_custom: '' + row_class_default: true + row_class_custom: '' + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + thumbnail__target_id: + id: thumbnail__target_id + table: media_field_data + field: thumbnail__target_id + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: image + settings: + image_style: browser_thumbnail + image_link: '' + group_column: '' + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: media + entity_field: thumbnail + plugin_id: field + entity_browser_select: + id: entity_browser_select + table: media + field: entity_browser_select + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: visually-hidden + element_default_classes: false + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + entity_type: media + plugin_id: entity_browser_select + filters: + name: + id: name + table: media_field_data + field: name + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: name_op + label: 'Image name' + description: '' + use_operator: false + operator: name_op + identifier: name + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: media + entity_field: name + plugin_id: string + bundle: + id: bundle + table: media_field_data + field: bundle + relationship: none + group_type: group + admin_label: '' + operator: in + value: + image: image + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: media + entity_field: bundle + plugin_id: bundle + status: + id: status + table: media_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Publishing status' + description: '' + use_operator: false + operator: status_op + identifier: status + required: true + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: boolean + entity_type: media + entity_field: status + field_media_in_library_value: + id: field_media_in_library_value + table: media__field_media_in_library + field: field_media_in_library_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: boolean + sorts: + created: + id: created + table: media_field_data + field: created + relationship: none + group_type: group + admin_label: '' + order: DESC + exposed: true + expose: + label: Created + granularity: second + entity_type: media + entity_field: created + plugin_id: date + name: + id: name + table: media_field_data + field: name + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: true + expose: + label: 'Media name' + entity_type: media + entity_field: name + plugin_id: standard + header: { } + footer: { } + empty: + area: + id: area + table: views + field: area + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: + value: 'No results found' + format: full_html + plugin_id: text + relationships: { } + arguments: { } + display_extenders: { } + title: 'Images Library' + filter_groups: + operator: AND + groups: + 1: AND + css_class: eb-media + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'url.query_args:sort_by' + - 'url.query_args:sort_order' + - user.permissions + tags: { } + images_library: + display_plugin: entity_browser + id: images_library + display_title: 'Entity browser' + position: 1 + display_options: + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - 'url.query_args:sort_by' + - 'url.query_args:sort_order' + - user.permissions + tags: { } diff --git a/modules/openy_ds_media_library/config/install/views.view.digital_signage_media.yml b/modules/openy_ds_media_library/config/install/views.view.digital_signage_media.yml new file mode 100644 index 0000000..b8f3e74 --- /dev/null +++ b/modules/openy_ds_media_library/config/install/views.view.digital_signage_media.yml @@ -0,0 +1,807 @@ +uuid: c679ca7e-3973-4ca5-b27c-190e27864799 +langcode: en +status: true +dependencies: + config: + - image.style.thumbnail + - system.menu.admin + module: + - image + - media_entity + - user +_core: + default_config_hash: i7kD7PlSfZhwyTiOigjWRiiTzuVJjp5dsvjRbSDvP5c +id: digital_signage_media +label: 'Digital Signage Media' +module: views +description: '' +tag: '' +base_table: media_field_data +base_field: mid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access media overview' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: full + options: + items_per_page: 50 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: '‹ Previous' + next: 'Next ›' + first: '« First' + last: 'Last »' + quantity: 9 + style: + type: table + options: + grouping: { } + row_class: '' + default_row_class: true + override: true + sticky: false + caption: '' + summary: '' + description: '' + columns: + name: name + bundle: bundle + changed: changed + uid: uid + status: status + thumbnail__target_id: thumbnail__target_id + info: + name: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + bundle: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + changed: + sortable: true + default_sort_order: desc + align: '' + separator: '' + empty_column: false + responsive: '' + uid: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + status: + sortable: true + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + thumbnail__target_id: + sortable: false + default_sort_order: asc + align: '' + separator: '' + empty_column: false + responsive: '' + default: changed + empty_table: false + row: + type: fields + fields: + thumbnail__target_id: + id: thumbnail__target_id + table: media_field_data + field: thumbnail__target_id + relationship: none + group_type: group + admin_label: '' + label: Thumbnail + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: image + settings: + image_style: thumbnail + image_link: '' + group_column: '' + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: media + entity_field: thumbnail + plugin_id: field + name: + id: name + table: media_field_data + field: name + entity_type: media + entity_field: media + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: 'Media name' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + bundle: + id: bundle + table: media_field_data + field: bundle + relationship: none + group_type: group + admin_label: '' + label: Provider + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: true + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: media + entity_field: bundle + plugin_id: field + uid: + id: uid + table: media_field_data + field: uid + relationship: none + group_type: group + admin_label: '' + label: Author + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: target_id + type: entity_reference_label + settings: + link: true + group_column: target_id + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: media + entity_field: uid + plugin_id: field + status: + id: status + table: media_field_data + field: status + relationship: none + group_type: group + admin_label: '' + label: Status + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: boolean + settings: + format: custom + format_custom_true: Published + format_custom_false: Unpublished + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: media + entity_field: status + plugin_id: field + changed: + id: changed + table: media_field_data + field: changed + relationship: none + group_type: group + admin_label: '' + label: Updated + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: timestamp + settings: + date_format: short + custom_date_format: '' + timezone: '' + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: media + entity_field: changed + plugin_id: field + operations: + id: operations + table: media + field: operations + relationship: none + group_type: group + admin_label: '' + label: Operations + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: true + entity_type: media + plugin_id: entity_operations + filters: + status: + id: status + table: media_field_data + field: status + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '1' + group: 1 + exposed: true + expose: + operator_id: '' + label: 'True' + description: null + use_operator: false + operator: status_op + identifier: status + required: true + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: true + group_info: + label: 'Publishing status' + description: '' + identifier: status + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: + 1: + title: Published + operator: '=' + value: '1' + 2: + title: Unpublished + operator: '=' + value: '0' + plugin_id: boolean + entity_type: media + entity_field: status + bundle: + id: bundle + table: media_field_data + field: bundle + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: bundle_op + label: Provider + description: '' + use_operator: false + operator: bundle_op + identifier: provider + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: media + entity_field: bundle + plugin_id: bundle + name: + id: name + table: media_field_data + field: name + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: true + expose: + operator_id: name_op + label: 'Media name' + description: '' + use_operator: false + operator: name_op + identifier: name + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: media + entity_field: name + plugin_id: string + langcode: + id: langcode + table: media_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + operator: in + value: { } + group: 1 + exposed: true + expose: + operator_id: langcode_op + label: Language + description: '' + use_operator: false + operator: langcode_op + identifier: langcode + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + reduce: false + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + entity_type: media + entity_field: langcode + plugin_id: language + sorts: + created: + id: created + table: media_field_data + field: created + order: DESC + entity_type: media + entity_field: created + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + title: Media + header: { } + footer: { } + empty: + area_text_custom: + id: area_text_custom + table: views + field: area_text_custom + relationship: none + group_type: group + admin_label: '' + empty: true + tokenize: false + content: 'No content available.' + plugin_id: text_custom + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } + media_page_list: + display_plugin: page + id: media_page_list + display_title: Media + position: 1 + display_options: + display_extenders: { } + path: admin/digital-signage/media + menu: + type: normal + title: Media + description: 'Individual slides and images' + expanded: false + parent: system.admin_openy_digital_signage + weight: 0 + context: '0' + menu_name: admin + display_description: '' + cache_metadata: + max-age: 0 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - url.query_args + - user.permissions + tags: { } diff --git a/modules/openy_ds_media_library/css/browser.css b/modules/openy_ds_media_library/css/browser.css new file mode 100644 index 0000000..8953a47 --- /dev/null +++ b/modules/openy_ds_media_library/css/browser.css @@ -0,0 +1,7 @@ +/** + * @file browser.css + */ + +.view.view-digital-signage-images-library .views-col.selected img { + box-shadow: 0 0 10px #0089d0; +} diff --git a/modules/openy_ds_media_library/openy_ds_media_library.info.yml b/modules/openy_ds_media_library/openy_ds_media_library.info.yml new file mode 100644 index 0000000..78c1c14 --- /dev/null +++ b/modules/openy_ds_media_library/openy_ds_media_library.info.yml @@ -0,0 +1,9 @@ +name: Open Y Digital Signage Media Library +type: module +description: Provides separate Media library to Open Y Digital Signage. +core: 8.x +package: "OpenY: Digital Signage" +dependencies: + - media_entity + - openy_media_image + - entity_browser diff --git a/modules/openy_ds_media_library/openy_ds_media_library.install b/modules/openy_ds_media_library/openy_ds_media_library.install new file mode 100644 index 0000000..40642dc --- /dev/null +++ b/modules/openy_ds_media_library/openy_ds_media_library.install @@ -0,0 +1,33 @@ + DigitalSignageMediaLibrary::DS_MARKER_TAG_NAME, + 'vid' => 'media_tags', + ])->save(); +} + +/** + * Implements hook_uninstall(). + */ +function openy_ds_media_library_uninstall() { + /** @var Drupal\openy_ds_media_library\DigitalSignageMediaLibrary $media_library_helper */ + $media_library_helper = \Drupal::service('openy_ds_media_library.helper'); + $term = $media_library_helper->loadMarkerMediaTag(); + if ($term) { + $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); + $storage->delete([$term]); + } +} diff --git a/modules/openy_ds_media_library/openy_ds_media_library.libraries.yml b/modules/openy_ds_media_library/openy_ds_media_library.libraries.yml new file mode 100644 index 0000000..d30a848 --- /dev/null +++ b/modules/openy_ds_media_library/openy_ds_media_library.libraries.yml @@ -0,0 +1,7 @@ +browser: + version: VERSION + css: + theme: + css/browser.css: {} + dependencies: + - openy_media/browser diff --git a/modules/openy_ds_media_library/openy_ds_media_library.module b/modules/openy_ds_media_library/openy_ds_media_library.module new file mode 100644 index 0000000..2827b94 --- /dev/null +++ b/modules/openy_ds_media_library/openy_ds_media_library.module @@ -0,0 +1,146 @@ +loadMarkerMediaTag(); + $views_join_plugin_manager = Drupal::service('plugin.manager.views.join'); + + // digital signage media must be excluded from default media dashboard, + // and must be displayed on special digital signage media dashboard instead. + if ($term) { + switch ($view->id()) { + case 'media': + $definition = _openy_ds_media_library_get_media_tag_join_definition(); + $definition['extra'][] = [ + 'field' => 'field_media_tags_target_id', + 'value' => $term->id(), + ]; + + /** @var \Drupal\views\Plugin\views\join\Standard $join */ + $join = $views_join_plugin_manager->createInstance('standard', $definition); + /** @var \Drupal\views\Plugin\views\query\Sql $query */ + $query->addRelationship('media__field_media_tags', $join, 'node'); + $query->addWhereExpression(1, 'media__field_media_tags.field_media_tags_target_id IS NULL'); + break; + + case 'digital_signage_media': + case 'digital_signage_images_library': + $definition = _openy_ds_media_library_get_media_tag_join_definition(); + /** @var \Drupal\views\Plugin\views\join\Standard $join */ + $join = $views_join_plugin_manager->createInstance('standard', $definition); + /** @var \Drupal\views\Plugin\views\query\Sql $query */ + $query->addRelationship('media__field_media_tags', $join, 'node'); + $query->addWhere(1, 'media__field_media_tags.field_media_tags_target_id', $term->id()); + break; + } + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function openy_ds_media_library_form_entity_browser_digital_signage_images_library_form_alter(&$form, FormStateInterface $form_state, $form_id) { + $form['#attached']['library'][] = 'openy_ds_media_library/browser'; +} + +/** + * Implements hook_preprocess_HOOK(). + */ +function openy_ds_media_library_preprocess_views_view_grid(array &$variables) { + if ($variables['view']->storage->id() == 'digital_signage_images_library') { + foreach ($variables['items'] as &$item) { + foreach ($item['content'] as &$column) { + $column['attributes']['data-selectable'] = 'true'; + } + } + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function openy_ds_media_library_form_media_image_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) { + /** @var Drupal\openy_ds_media_library\DigitalSignageMediaLibrary $media_library_helper */ + $media_library_helper = \Drupal::service('openy_ds_media_library.helper'); + $term = $media_library_helper->loadMarkerMediaTag(); + /** @var \Drupal\media_entity\Entity\Media $media_entity */ + $media_entity = $form_state->getFormObject()->getEntity(); + + // Digital signage media is already marked with special tag on edit. + if ($term && !$media_entity->get('field_media_tags')->isEmpty()) { + $media_tags = $media_entity->get('field_media_tags')->getValue(); + if (in_array($term->id(), array_column($media_tags, 'target_id'))) { + _openy_ds_media_library_set_media_form_default_values($form); + } + } +} + +/** + * Implements hook_inline_entity_form_entity_form_alter(). + */ +function openy_ds_media_library_inline_entity_form_entity_form_alter(&$entity_form, &$form_state) { + /** @var Drupal\openy_ds_media_library\DigitalSignageMediaLibrary $media_library_helper */ + $media_library_helper = \Drupal::service('openy_ds_media_library.helper'); + $term = $media_library_helper->loadMarkerMediaTag(); + + // Automatically mark media entity created in digital signage entity browser. + // So we can to distinguish common media and digital signage media entities. + $input = $form_state->getUserInput(); + if ($term && isset($input['form_id']) && $input['form_id'] == 'entity_browser_digital_signage_images_library_form') { + $entity_form['field_media_tags']['widget']['target_id']['#default_value'] = $term; + _openy_ds_media_library_set_media_form_default_values($entity_form); + } +} + +/** + * Gets views join plugin definition for relation to media tags taxonomy. + * + * @return array + * The array of definition data. + */ +function _openy_ds_media_library_get_media_tag_join_definition() { + return [ + 'table' => 'media__field_media_tags', + 'field' => 'entity_id', + 'left_table' => 'media_field_data', + 'left_field' => 'mid', + ]; +} + +/** + * Sets default values and hide unnecessary fields. + * + * @param $form + * Nested array of form elements that comprise the form. + */ +function _openy_ds_media_library_set_media_form_default_values(&$form) { + // @TODO most of fields will be hidden after solving of entity_browser issue. + // @see https://www.drupal.org/project/entity_browser/issues/2821917 + $form['field_media_tags']['#access'] = FALSE; + + $image_widget = &$form['field_media_image']['widget']; + $image_widget[0]['#alt_field'] = FALSE; + $image_widget['#title_field_required'] = FALSE; + + $form['field_media_caption']['#access'] = FALSE; + + $form['field_media_in_library']['widget']['value']['#default_value'] = TRUE; + $form['field_media_in_library']['#access'] = FALSE; + + $form['langcode']['#access'] = FALSE; + $form['path']['#access'] = FALSE; + $form['revision_information']['#access'] = FALSE; +} diff --git a/modules/openy_ds_media_library/openy_ds_media_library.services.yml b/modules/openy_ds_media_library/openy_ds_media_library.services.yml new file mode 100644 index 0000000..eb16180 --- /dev/null +++ b/modules/openy_ds_media_library/openy_ds_media_library.services.yml @@ -0,0 +1,4 @@ +services: + openy_ds_media_library.helper: + class: Drupal\openy_ds_media_library\DigitalSignageMediaLibrary + arguments: ['@entity_type.manager'] diff --git a/modules/openy_ds_media_library/src/DigitalSignageMediaLibrary.php b/modules/openy_ds_media_library/src/DigitalSignageMediaLibrary.php new file mode 100644 index 0000000..c181c16 --- /dev/null +++ b/modules/openy_ds_media_library/src/DigitalSignageMediaLibrary.php @@ -0,0 +1,48 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * Loads taxonomy term which is used as marker for DS media entities. + * + * @return \Drupal\Core\Entity\EntityInterface|mixed + * Taxonomy term entity. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + public function loadMarkerMediaTag() { + $storage = $this->entityTypeManager->getStorage('taxonomy_term'); + $properties = ['name' => static::DS_MARKER_TAG_NAME, 'vid' => 'media_tags']; + $terms = $storage->loadByProperties($properties); + if ($terms) { + return reset($terms); + } + + return NULL; + } + +} diff --git a/modules/openy_ds_pef_schedule/openy_ds_pef_schedule.info.yml b/modules/openy_ds_pef_schedule/openy_ds_pef_schedule.info.yml new file mode 100644 index 0000000..ea8ba24 --- /dev/null +++ b/modules/openy_ds_pef_schedule/openy_ds_pef_schedule.info.yml @@ -0,0 +1,7 @@ +name: Digital Signage Classes PEF Schedule integration +type: module +description: Digital Signage Classes Schedule. +core: 8.x +package: "OpenY: Digital Signage" +dependencies: + - openy_ds_room_pef diff --git a/modules/openy_ds_pef_schedule/openy_ds_pef_schedule.services.yml b/modules/openy_ds_pef_schedule/openy_ds_pef_schedule.services.yml new file mode 100644 index 0000000..89cf9fc --- /dev/null +++ b/modules/openy_ds_pef_schedule/openy_ds_pef_schedule.services.yml @@ -0,0 +1,4 @@ +services: + openy_ds_pef_schedule.manager: + class: Drupal\openy_ds_pef_schedule\OpenYPEFScheduleManager + arguments: ['@entity.manager', '@entity.query', '@logger.factory', '@database'] diff --git a/modules/openy_ds_pef_schedule/src/OpenYPEFScheduleManager.php b/modules/openy_ds_pef_schedule/src/OpenYPEFScheduleManager.php new file mode 100644 index 0000000..0d5844f --- /dev/null +++ b/modules/openy_ds_pef_schedule/src/OpenYPEFScheduleManager.php @@ -0,0 +1,332 @@ +entityQuery = $entity_query; + $this->entityTypeManager = $entity_type_manager; + $this->logger = $logger_factory->get(self::CHANNEL); + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public function getClassesSchedule($period, $nextday, $location = null, $room_id = [], $category = []) { + $datetime = new \DateTime(); + $datetime->setTimezone(new \DateTimeZone('UTC')); + $datetime->setTimestamp($period['from']); + $period_from = $datetime->format('c'); + $datetime->add(new \DateInterval('P1D')); + $period_nextday = $datetime->format('c'); + + $rooms = []; + + if ($room_id) { + foreach ($room_id as $rid) { + $room = $this->entityTypeManager + ->getStorage('openy_ds_room') + ->load($rid); + + foreach ($room->field_room_origin as $value) { + if ($value->origin != 'pef') { + continue; + } + $rooms[] = $value->id; + } + } + } + + if (!$location && $room) { + $location = $room->location->target_id; + } + + $results = $this->getDataForADate($period_from, $location, $rooms, $category); + switch ($nextday) { + case static::NEXT_DAY_ALWAYS: + $results_nd = $this->getDataForADate($period_nextday, $location, $rooms, $category); + foreach ($results_nd as $result) { + $results[] = $result; + } + break; + case static::NEXT_DAY_IF_EMPTY: + if (!$results) { + $results = $this->getDataForADate($period_nextday, $location, $rooms, $category); + } + break; + } + + return $results; + } + + /** + * Retrieves class schedule for a whole day. + * + * @param string $date + * Date string. + * @param $location + * Location Id + * @param array $rooms + * @param array $category + * + * @return array + */ + private function getDataForADate($date, $location, $rooms = [], $category = []) { + $date = strtotime($date); + + $year = date('Y', $date); + $month = date('m', $date); + $day = date('d', $date); + $week = date('W', $date); + $weekday = date('N', $date); + + $timestamp_start = $date; + // Next day. + $timestamp_end = $date + 24 * 60 * 60; + + $sql = "SELECT DISTINCT + n.nid, + re.id, + nd.title as location, + nds.title as name, + re.class, + re.session, + re.room, + re.instructor as instructor, + re.category, + re.register_url as register_url, + re.register_text as register_text, + re.start as start_timestamp, + re.end as end_timestamp, + re.duration as duration + FROM {node} n + RIGHT JOIN {repeat_event} re ON re.session = n.nid + INNER JOIN node_field_data nd ON re.location = nd.nid + INNER JOIN node_field_data nds ON n.nid = nds.nid + WHERE + n.type = 'session' + AND + ( + (re.year = :year OR re.year = '*') + AND + (re.month = :month OR re.month = '*') + AND + (re.day = :day OR re.day = '*') + AND + (re.week = :week OR re.week = '*') + AND + (re.weekday = :weekday OR re.weekday = '*') + AND + (re.start <= :timestamp_end) + AND + (re.end >= :timestamp_start) + AND + (re.location = :location) + )"; + + $values = []; + if (!empty($category)) { + $sql .= "AND re.category IN ( :categories[] )"; + $values[':categories[]'] = $category; + } + + if (!empty($rooms)) { + $sql .= "AND re.room IN ( :rooms[] )"; + $values[':rooms[]'] = $rooms; + } + + $values[':location'] = $location; + $values[':year'] = $year; + $values[':month'] = $month; + $values[':day'] = $day; + $values[':week'] = $week; + $values[':weekday'] = $weekday; + $values[':timestamp_start'] = $timestamp_start; + $values[':timestamp_end'] = $timestamp_end; + + $query = $this->database->query($sql, $values); + $results = $query->fetchAll(); + + $classes = []; + foreach ($results as $result) { + $from = $result->start_timestamp; + $to = $result->end_timestamp; + $from_str = $year . '-' . $month . '-' . $day . ' ' . date('H:i:s', $from); + $to_str = $year . '-' . $month . '-' . $day . ' ' . date('H:i:s', $to); + $duration_str = $result->duration . 'm'; + if ($result->duration > 90) { + $duration_str = intval($result->duration / 60) . 'h ' . ($result->duration % 60) . 'm'; + } + $from = strtotime($from_str); + $to = strtotime($to_str); + $classes[] = [ + 'from' => $from, + 'to' => $to, + 'duration_raw' => $result->duration, + 'duration' => $duration_str, + 'room' => $result->room, + 'trainer' => $this->prepareTrainerName($result->instructor), + 'substitute_trainer' => '', + 'name' => $this->prepareClassName($result->name), + 'from_formatted' => date('g:ia', $from), + 'to_formatted' => date('g:ia', $to), + ]; + } + + usort($classes, function ($a, $b) { + if ($a['from'] == $b['from']) { + return 0; + } + return ($a['from'] < $b['from']) ? -1 : 1; + }); + + return $classes; + } + + /** + * Prepare class name to display. + * + * @param string $name + * Class name. + * + * @return string + * Prepared to display class name. + */ + protected function prepareClassName($name) { + $name = str_replace('®', '®', trim($name)); + $name = str_replace('™', '', $name); + + return $name; + } + + /** + * Truncate last name into short version. + * + * @param string $name + * Trainer name. + * + * @return string + * Return first name and only first letter of last name. + */ + protected function prepareTrainerName($name) { + $new_name = ''; + if (empty($name)) { + return $new_name; + } + // Divide name into 2 parts. + $array = explode(' ', trim($name)); + $array = array_values(array_filter($array, 'trim')); + // Add first name to the new name. + $new_name .= $array[0]; + if (empty($array[1])) { + return $new_name; + } + // Verify is last name full or already cut to one symbol and point. + if (strlen($array[1]) == 2 && substr($array[1], 1, 1) == '.') { + // Leave as is. + $new_name .= ' ' . $array[1]; + } + else { + // Add only first latter of last name.. + $new_name .= ' ' . strtoupper(substr($array[1], 0, 1)) . '.'; + } + + return $new_name; + } + + public function getAllCategories() { + $query = $this->database + ->select('node_field_data', 'n') + ->fields('n', ['title']) + ->condition('n.type', 'activity') + ->condition('n.status', NodeInterface::PUBLISHED) + ->orderBy('n.title', 'ASC'); + + $result = $query->execute()->fetchAllKeyed(0, 0); + natsort($result); + + return $result; + } + + /** + * Returns NEXT_DAY_NEVER constant. + * + * @return int + */ + public static function getNextDayNever() { + return static::NEXT_DAY_NEVER; + } + + /** + * Returns NEXT_DAY_ALWAYS constant. + * + * @return int + */ + public static function getNextDayAlways() { + return static::NEXT_DAY_ALWAYS; + } + + /** + * Returns NEXT_DAY_IF_EMPTY constant. + * + * @return int + */ + public static function getNextDayIfEmpty() { + return static::NEXT_DAY_IF_EMPTY; + } + +} diff --git a/modules/openy_ds_pef_schedule/src/OpenYPEFScheduleManagerInterface.php b/modules/openy_ds_pef_schedule/src/OpenYPEFScheduleManagerInterface.php new file mode 100644 index 0000000..aba91a8 --- /dev/null +++ b/modules/openy_ds_pef_schedule/src/OpenYPEFScheduleManagerInterface.php @@ -0,0 +1,31 @@ +