# Lockscreen

*By* [*adamlubek*](https://github.com/adamlubek)

This recipe demonstrates RxJS implementation of lockscreen functionality (known for example from smartphones).

### Example Code

( [StackBlitz](https://stackblitz.com/edit/rxjs-lockscreen?file=index.ts\&devtoolsheight=30) )

![Lockscreen](https://drive.google.com/uc?export=view\&id=1EknMWCVag08IuecwP4UppUR7fKn3qd-2)

#### index.ts

```js
/*
  Use mouse to 'swipe' across the lock pad (hold mouse button and swipe :) ).
  Pad will turn green if password is correct or red if password is incorrect.
  You can set password to whatever sequence you like.
*/
// RxJS v6+
import { from, fromEvent, Subject, merge, pipe } from 'rxjs';
import {
  switchMap,
  takeUntil,
  repeat,
  tap,
  map,
  throttleTime,
  distinctUntilChanged,
  filter,
  toArray,
  sequenceEqual,
  pluck
} from 'rxjs/operators';
import {
  displaySelectedNumbersSoFar,
  markTouchedPad,
  pads,
  resetPasswordPad,
  setResult
} from './dom-updater';

const sub = new Subject();
const expectedPasswordUpdate$ = fromEvent(
  document.getElementById('expectedPassword'),
  'keyup'
).pipe(
  map((e: any) => e.target.value),
  tap(pass => sub.next(pass.split('').map(e => parseInt(e))))
);
let expectedPassword = [1, 2, 5, 2];
const expectedPassword$ = sub.pipe(tap((v: any) => (expectedPassword = v)));

const takeMouseSwipe = pipe(
  // take mouse moves
  switchMap(_ => fromEvent(document, 'mousemove')),
  // once mouse is up, we end swipe
  takeUntil(fromEvent(document, 'mouseup')),
  throttleTime(50)
);
const checkIfPasswordMatch = password =>
  from(password).pipe(sequenceEqual(from(expectedPassword)));
const getXYCoordsOfMousePosition = ({ clientX, clientY }: MouseEvent) => ({
  x: clientX,
  y: clientY
});
const findSelectedPad = v =>
  pads.find(
    r => v.x > r.left && v.x < r.right && v.y > r.top && v.y < r.bottom
  );
const getIdOfSelectedPad = pipe(
  filter(v => !!v),
  pluck('id'),
  distinctUntilChanged()
);

const actualPassword$ = fromEvent(document, 'mousedown').pipe(
  // new stream so reset password pad and take swipe until mouse up
  tap(resetPasswordPad),
  takeMouseSwipe,
  // as we swipe, we mark pads as touched and display selected numbers
  map(getXYCoordsOfMousePosition),
  map(findSelectedPad),
  getIdOfSelectedPad,
  tap(markTouchedPad),
  tap(displaySelectedNumbersSoFar),
  // we need an array of numbers from current swipe which we can pass to checkIfPasswordMatch
  toArray(),
  // on mouse up (swipe end), switchMap to new stream to check if password match
  switchMap(checkIfPasswordMatch),
  tap(setResult),
  // takeUntil inside takeMouseSwipe terminated stream so we repeat from beginning (mousedown)
  repeat()
);

merge(expectedPassword$, expectedPasswordUpdate$, actualPassword$).subscribe();
```

#### dom-updater.ts

```js
const createPadObject = (id, rectange) => ({
  id: id,
  left: rectange.left,
  right: rectange.right,
  top: rectange.top,
  bottom: rectange.bottom
});

const setResultText = text =>
  (document.getElementById('result').innerText = text);

const setPasswordPads = color =>
  Array.from(document.querySelectorAll('.cell')).forEach(
    (v: HTMLElement) => (v.style.background = color)
  );

const getPad = id => document.getElementById(`c${id}`);

export const pads = Array.from({ length: 9 }, (_, n) => n + 1).map(v =>
  createPadObject(v, getPad(v).getBoundingClientRect())
);

export const markTouchedPad = v => {
  const pad = getPad(v);
  pad.style.background = 'lightgrey';
  if (!pad.animate) return; //animate does not work in IE
  const animation: any = [
    { transform: 'scale(0.9)' },
    { transform: 'scale(1)' }
  ];
  const animationOptions = {
    duration: 300,
    iterations: 1
  };
  pad.animate(animation, animationOptions);
  document.getSelection().removeAllRanges();
};

export const setResult = result => {
  setPasswordPads(result ? 'MediumSeaGreen' : 'IndianRed');
  setResultText('Password ' + (result ? 'matches :)' : 'does not match :('));
};

export const displaySelectedNumbersSoFar = v =>
  (document.getElementById('result').textContent += v);

export const resetPasswordPad = () => {
  setResultText('');
  setPasswordPads('gray');
};
```

**html**

```html
<style>
  .grid {
    border-spacing: 2px;
  }
  .cell {
    width: 50px;
    height: 50px;
    background: grey;
    display: table-cell;
    border-radius: 50%;
    transform: scale(0.5);
    text-align: center;
    vertical-align: middle;
    color: white;
  }

  .pulse div {
    animation-name: pulse;
    animation-duration: 2s;
    animation-iteration-count: infinite;
  }

  @keyframes pulse {
    from {
      transform: scale(1);
    }
    50% {
      transform: scale(0.99);
    }
    to {
      transform: scale(1);
    }
  }
</style>

Expected Password:
<input id="expectedPassword" value="1252" />
<hr />
Password:
<div class="grid pulse">
  <div>
    <div class="cell" id="c1">1</div>
    <div class="cell" id="c2">2</div>
    <div class="cell" id="c3">3</div>
  </div>
  <div>
    <div class="cell" id="c4">4</div>
    <div class="cell" id="c5">5</div>
    <div class="cell" id="c6">6</div>
  </div>
  <div>
    <div class="cell" id="c7">7</div>
    <div class="cell" id="c8">8</div>
    <div class="cell" id="c9">9</div>
  </div>
</div>

<div id="result"></div>
```

### Operators Used

* [distinctUntilChanged](/learn-rxjs/operators/filtering/distinctuntilchanged.md)
* [filter](/learn-rxjs/operators/filtering/filter.md)
* [from](/learn-rxjs/operators/creation/from.md)
* [fromEvent](/learn-rxjs/operators/creation/fromevent.md)
* [map](/learn-rxjs/operators/transformation/map.md)
* [merge](/learn-rxjs/operators/combination/merge.md)
* [pluck](/learn-rxjs/operators/transformation/pluck.md)
* [repeat](/learn-rxjs/operators/utility/repeat.md)
* [sequenceEqual](/learn-rxjs/operators/conditional/sequenceequal.md)
* [Subject](/learn-rxjs/subjects/subject.md)
* [switchMap](/learn-rxjs/operators/transformation/switchmap.md)
* [takeUntil](/learn-rxjs/operators/filtering/takeuntil.md)
* [tap](/learn-rxjs/operators/utility/do.md)
* [throttleTime](/learn-rxjs/operators/filtering/throttletime.md)
* [toArray](/learn-rxjs/operators/transformation/toarray.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://www.learnrxjs.io/learn-rxjs/recipes/lockscreen.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
