Home

Awesome

NOTICE for MM >2.25 USERS

Use MMM-Scenes2 instead of this module from MM 2.25.

MMM-Scenes

“Life is a theatre set in which there are but few practicable entrances.”

― Victor Hugo, Les Misérables

MagicMirror module to change screen scenes by time and order with ANIMATION EFFECT.

Demo

MMM-Scenes Demo Click To Play

TOC

Concept

The scenario of the MM screen is made up of a series of scenes. Each module has its role in its appearance scenes to enter and exit by design.

When a scene begins, all modules whose roles end, will be expelled, and all modules that have the parts in that scene will be admitted.

As described in the scenario, your MM screen will play a drama with modules.

Features

Install

cd ~/MagicMirror/modules
git clone https://github.com/MMRIZE/MMM-Scenes
cd MMM-Scenes
npm install

Config

{
  module: "MMM-Scenes",
  position: 'bottom_right', // Position of indicator
  classes: "scene1 scene2 scene3", // Even indicator needs the direction when it is presented.
  hiddenOnStartup: true,
  config: {
    scenario: [ "scene1", "scene2", "scene3"],
    // other options...
  }
}

You can find an example in th examples directory.

Global options

These options will apply to each scene unless defined in the scene newly. All the properties are omittable, and if omitted, a default value will be used.

propertydefaultdescription
scenario[]The order-set of scenes. You can set the scene name(class name) or scene definition (object) as the items of this property.
autoLoop"infinity"Looping scenes in scenario. <br> Available values: "no", "once", "infinity" <br> Default Value: "infinity"
startScene''(optional) If set, this scene will be displayed on startup. If omitted, the first scene of scenario will be displayed at first.
duration10000(ms) When the next scene will come after the current scene starts. On autoLoop = no, this will be ignored.
admitAnimation"default"When the scene starts, how the modules will appear. See animation
admitDuration1000(ms) speed of admitAnimation
admitGap0(ms) the entrance gap of modules in admitAnimation<br> In admitGap = 0 all roled modules will enter together, in admitGap = 500 each roled module will enter sequentially with 500ms of delay.
expelAnimation"default"Before the new scene starts, how unrolled modules will be expelled. See animation
expelDuration1000(ms) speed of expelAnimation
expelGap0(ms) the exit gap of modules in expelAnimation
lockString"mmm-scenes"Lock-key of MM's locking mechanism. Generally, you don't need to touch.
inactiveIndicators['○']Array of inactive scene indicators See indicators
activeIndicators['●']Array of active scene indicators. See indicators

Assign scene to modules

Usually, A scene is defined as text. If you want some module to admit into the specific scene, give the scene name as the module's class name.

{
  module: "clock",
  position: "top_left",
  classes: "scene1 scene3"
},
{
  module: "helloworld",
  position: "top_right",
  classes: "scene2"
}

In this example, the clock module will appear in scene1 and scene3 and will be expelled from scene2 or scene4. helloworld module will appear only in scene2.

Describe scenes in scenario

In the module's configuration, you can describe scenario with which scenes will appear by order.

config: {
  scenario: ["scene1", "scene2", "scene1", "scene3", "scene1", "scene4"],
}

All the scenes have the same global properties by default. But you can specify a scene with scene definitions in scenario.

config: {
  duration: 10 * 60 * 1000,
  scenario: [
    "scene1", 
    "scene2",
    {
      name: "scene3",
      duration: 30 * 60 * 1000,
      admitAnimation: "jelly"
    },
    "scene4"
  ],
}

In this example, scene1, scene2 and scene4 will have 10 * 60 * 1000 as duration and default animations, but scene3 will have 30 * 60 * 1000 with jelly style admitAnimation.

scene object (scene3) should have name property. Other available properties are admitAnimation, admitDuration, admitGap, expelAnimation, expelDuration, expelGap and duration

Animation

admitAnimation and expelAnimation would point which animation effect would be applied to each scene transition. They could have three types of values as a definition of animation.

- predefined animation name

Some predefined animation behaviours are prepared in animation.mjs file. You can select one of them for your animation effect.

expelAnimation: "jelly",
admitAnimation: "pageDown"

- keyframe array or object

Or you can set a keyframe array or object for your own custom animation. (See reference)

expelAnimation: [ // keyframe array
  { opacity: 1, transform: 'scale(1, 1)' },
  { opacity: 0.5, transform: 'scale(0.5, 1)' },
  { opacity: 0, transform: 'scale(0, 0)' }
],
admitAnimation: { // keyframe object
  opacity: [ 0, 0.9, 1 ],
  offset: [ 0, 0.8 ], 
  easing: [ 'ease-in', 'ease-out' ],
},

- custom animation function

Or you can assign your custom animation function to control more detailly. The animation function should return Promise or be async function to know when the animation finishes.

expelAnimation: async ({ moduleWrapper, duration }) => {
  const asleep = (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
  moduleWrapper.style.transition = 'opacity ' + duration / 1000 + 's'
  moduleWrapper.style.opacity = 0
  await asleep(duration)
  return
},
admitAnimation: ({ moduleWrapper, duration, module }) => {
  const position = (pos) => {
    if (pos.search("_left") >= 0) return "(-50vw, 0)";
    if (pos.search("_right") >= 0) return "(50vw, 0)";
    if (pos.search("top_bar") >= 0 || pos.search("upper") >= 0 || pos.search("middle") >= 0) return "(0, 50vh)";
    if (pos.search("bottom_bar") >= 0 || pos.search("lower") >= 0) return "(0, 50vh)";
    return "(0,0)";
  };
  const value = position(module.data.position)
  let keyframe = [
    { transform: 'translate(0, 0)', opacity: 1, easing: 'ease-out' },
    { transform: `translate${value}`, opacity: 0, easing: 'ease-in' },
  ];
  return new Promise((resolve, reject) => {
    moduleWrapper.animate(keyframe, { duration }).onfinish = resolve
  })
},

You can also add your custom animation function into animation.mjs file, then use it's name simply.

An animation function will get DefinitionObject { moduleWrapper, module, duration, isExpel } argument. Return value has to be a Promise.

animation = async (DefinitionObject) => {
  const {
    moduleWrapper, // HTML Dom of target module
    module, // MM module itself
    duration, // Animation duration
    isExpel, // Only valid in 'animation.mjs'
  } = DefinitionObject
  console.log(module.name) // do your stuff
  ...
  return  
}

Notifications & WebAPI endpoints

Incoming notifications

this.sendNotification('SCENE_NEXT', {
  callback: (response) => { console.log(response.ok) }
})
this.sendNotification('SCENE_ACT', {
  index: 1,
  options: {
    duration: 30 * 60 * 1000,
    admitAnimation: 'jelly', // or keyframe or custom animation function
    admitDuration: 2000,
  }
})

SCENES_NEXT, payload: { callback, options }

Play next scene. If the current scene is not inside the scenario, the next scene will be the first scene(index: 0).

SCENES_PREV, payload: { callback, options }

Play previous scene. If the current scene is not inside of scenario, the previous scene doesn't exist.

SCENES_ACT, payload: { callback, options, name, index }

Play specific scene.

SCENES_CURRENT, payload: { callback }

Ask about the current scene. The callback function will get a data object as an argument.

this.sendNotification('SCENES_CURRENT' { callback: (info) => {
  console.log(info)
  /*
  `info` will have something like these;
  {
    scenario: [...],
    nextIndex,
    prevIndex,
    index,
    name,
    duration,
    options: { ... },
    ...
  }
  */
}})

Outgoing notification

SCENES_CHANGED, payload: { name, index, duration, scenario, autoLoop }

When a scene is changed, this notification will be emitted.

WebAPI endpoints

You can access MM url to control this module from outside of MM. e.g.) IFTTT.

GET /scenes/next

Play next scene.

GET /scenes/prev

Play previous scene

GET /scenes/act/:indexNumber

Play specific scene by given index

http://localhost/scenes/act/1

GET /scenes/act/:sceneName

Play specific scene by given name

http://localhost/scenes/act/scene2

TelegramBot integration

You can control MMM-Scenes using the Telegram app by installing the MMM-TelegramBot module and adding MMM-TelegramBot configuration to your scenes.

Telegram usage

Once installed and configured, you can control your MMM-Scenes display by sending messages in the Telegram app to your previously created Telegram Bot. The supported commands are as follows:

For example, to play the scene named 'scene1' in the scenario, issue the command:

/scene name:scene1

To play scene index 2 in the scenario, issue the command:

/scene_index 2

Indicators

inactiveIndicators: ['○'],
activeIndicators: ['●'],
inactiveIndicators: ['①', '②', '③'],
activeIndicators: ['❶', '❷', '❸']
inactiveIndicators: [''],
activeIndicators: ['Today', 'Family', 'Ent.'],
inactiveIndicators: ['1', '2', '3'],
activeIndicators: ['1', '2', '3'],
<div class="scenes_indicator">
  <span class="scenes_indicator_scene index_0 inactive first">○</span>
  <span class="scenes_indicator_scene index_1 active">●</span> <!-- current scene -->
  <span class="scenes_indicator_scene index_2 inactive last">○</span>
<div>
/* Just an example */
.scenes_indicator_scene {
  margin-left: 5px;
  margin-right: 5px;
  display: inline-block;
}

.scenes_indicator_scene.inactive {
  color: #999;
}

.scenes_indicator_scene.active {
  color: orange;
  font-weight: bold;
}

.scenes_indicator_scene.first::before {
  content: "[ "
}
.scenes_indicator_scene.last::after {
  content: " ]"
}

Predefined animation

Info

Memo

History

1.1.0 (2021-11-01)

1.0.0 (2021-10-12)

Author

ko-fi