-
-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: useBroadcastChannel, refShared and useSharedRef (#104)
* feat: useBroadcastChannel, refShared and useSharedRef * add support flag * WIP added leave event * allow master to be assigned randomly if the current master goes off * add better typescript to events * improve master set when targets disconnect * added broadcastChannel tests * add tests * Add documentation * changelog * cleanup
- Loading branch information
Showing
25 changed files
with
1,596 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<template> | ||
<div> | ||
<p> | ||
Supported: <b>{{ supported }}</b> | ||
</p> | ||
<p> | ||
To test please open 2 or more tabs, press send and all the other tabs | ||
should show the message | ||
</p> | ||
|
||
<p v-if="data">received: {{ data }}</p> | ||
|
||
<div> | ||
<input | ||
v-model="message" | ||
placeholder="Write a message" | ||
@keydown.enter="submitMessage" | ||
/> | ||
<button @click="submitMessage">send</button> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { useBroadcastChannel } from "vue-composable"; | ||
import { ref } from "@vue/composition-api"; | ||
export default { | ||
name: "broadcast-channel-example", | ||
setup() { | ||
const { supported, data, send } = useBroadcastChannel("composable-example"); | ||
const message = ref(""); | ||
const submitMessage = () => { | ||
send(message.value); | ||
message.value = ""; | ||
}; | ||
return { | ||
supported, | ||
data, | ||
message, | ||
submitMessage | ||
}; | ||
} | ||
}; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<template> | ||
<div> | ||
<p> | ||
To test please open 2 or more tabs and edit the input box | ||
</p> | ||
<input v-model="myRefVar" /> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { refShared } from "vue-composable"; | ||
export default { | ||
name: "ref-shared-example", | ||
setup() { | ||
const myRefVar = refShared("Hello world"); | ||
return { myRefVar }; | ||
} | ||
}; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<template> | ||
<div> | ||
<p>Supported: {{ supported }}</p> | ||
<p> | ||
Mind: {{ mindDescription }} <button @click="changeMind">Change</button> | ||
</p> | ||
<p>IsMaster: {{ master }}</p> | ||
|
||
<input v-model="data" :disabled="!editable" /> | ||
|
||
<p>targets: {{ targets.length }}</p> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { useSharedRef } from "vue-composable"; | ||
import { computed } from "@vue/composition-api"; | ||
export default { | ||
name: "shared-ref-example", | ||
setup() { | ||
const sharedRef = useSharedRef("test-shared-ref", 0); | ||
const mindDescription = computed(() => { | ||
switch (sharedRef.mind.value) { | ||
case 0: | ||
return "HIVE"; | ||
case 1: | ||
return "MASTER"; | ||
} | ||
}); | ||
const changeMind = () => { | ||
s.setMind((s.mind.value + 1) % 2); | ||
}; | ||
return { | ||
...sharedRef, | ||
mindDescription, | ||
changeMind | ||
}; | ||
} | ||
}; | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<template> | ||
<video style="width:100%" src="/master-hive.mp4" controls /> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
# useSharedRef | ||
|
||
> Share the same value across multiple tabs (within the same origin) in the same browser. | ||
::: warning | ||
|
||
This relies on [BroadcastChannel API](./../web/broadcastChannel.md). | ||
Safari doesn't support it. | ||
|
||
::: | ||
|
||
::: tip | ||
|
||
You can use short version [refShared](#refshared) | ||
|
||
::: | ||
|
||
## Parameters | ||
|
||
```js | ||
import { useSharedRef } from "vue-composable"; | ||
|
||
const sharedRef = useSharedRef(name, defaultValue?); | ||
``` | ||
| Parameters | Type | Required | Default | Description | | ||
| ------------ | -------- | -------- | ----------- | ---------------------------- | | ||
| name | `String` | `true` | | Name of the BroadcastChannel | | ||
| defaultValue | `T` | false | `undefined` | Default value | | ||
## State | ||
The `useSharedRef` function exposes the following reactive state: | ||
```js | ||
import { useSharedRef } from "vue-composable"; | ||
|
||
const { supported, id, data, master, mind, editable, targets } = useSharedRef(); | ||
``` | ||
| State | Type | Description | | ||
| --------- | --------------------------- | --------------------------------------------------------------------------------- | | ||
| supported | `boolean` | Returns true if [BroadcastChannel API](./../web/broadcastChannel.md) is supported | | ||
| id | `number` | Id of the reference | | ||
| data | `Ref<T>` | Value | | ||
| mind | `Ref<RefSharedMessageType>` | This describes on how this `ref` interacts with other tabs [check](#mind) | | ||
| master | `Ref<boolean>` | If this instance is `master` instance | | ||
| editable | `Ref<boolean>` | If this instance is editable | | ||
| targets | `Ref<number[]>` | Connected `ref` | | ||
## Methods | ||
The `useSharedRef` function exposes the following methods: | ||
```js | ||
import { useSharedRef } from "vue-composable"; | ||
|
||
const { ping, setMind, addListener } = useSharedRef(); | ||
``` | ||
| Signature | Description | | ||
| ------------------- | ---------------------------------------------- | | ||
| `ping()` | sends a ping to listeners | | ||
| `setMind(MindMode)` | sets all the listeners to the same mode | | ||
| `addListener` | adds a callback to Broadcast Channel `message` | | ||
## Information | ||
### Mind | ||
This is how the multiple tabs interact with each other, is possible to have two modes: | ||
- **0**: Hive (_default_) | ||
- **1**: Master | ||
```ts | ||
const { setMind } = useSharedRef(); | ||
// javascript | ||
setMind(0); // set mind to HIVE | ||
setMind(1); // set mind to MASTER | ||
|
||
// typescript | ||
import { SharedRefMind } from "vue-composable"; | ||
setMind(SharedRefMind.HIVE); // set mind to HIVE | ||
setMind(SharedRefMind.MASTER); // set mind to MASTER | ||
``` | ||
::: tip | ||
When a `useSharedRef(id)` is called it will try to sync with other tabs, `setMind` only needs to set in one intance. | ||
::: | ||
#### HIVE MODE | ||
This interaction allows the `ref` to be updated by everyone. | ||
If you have 5 tabs open every tab is allowed to update and everyone will sync to the latest modified value. | ||
#### MASTER MODE | ||
This interaction only allows the `master` to send updates to the other tabs. In `master` mode if the reference is not master the `data` **value changes will be ignored** | ||
::: tip | ||
If you want to change the master instance, please call `setMind(1)` on the instance you want to control, this will update all of the other. | ||
::: | ||
::: warning | ||
If the master instance gets destroyed (the component unmounted or tab closed) a new `master` will be assigned automatically (it will be the oldest instance) | ||
::: | ||
## Example | ||
<ClientOnly> | ||
<shared-ref-video/> | ||
</ClientOnly> | ||
<ClientOnly> | ||
<shared-ref-example/> | ||
</ClientOnly> | ||
### Code | ||
```vue | ||
<template> | ||
<div> | ||
<p>Supported: {{ supported }}</p> | ||
<p> | ||
Mind: {{ mindDescription }} <button @click="changeMind">Change</button> | ||
</p> | ||
<p>IsMaster: {{ master }}</p> | ||
|
||
<input v-model="data" :disabled="!editable" /> | ||
|
||
<p>targets: {{ targets.length }}</p> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { useSharedRef } from "vue-composable"; | ||
import { computed } from "@vue/composition-api"; | ||
|
||
export default { | ||
name: "shared-ref-example", | ||
setup() { | ||
const sharedRef = useSharedRef("test-shared-ref", 0); | ||
|
||
const mindDescription = computed(() => { | ||
switch (sharedRef.mind.value) { | ||
case 0: | ||
return "HIVE"; | ||
case 1: | ||
return "MASTER"; | ||
} | ||
}); | ||
|
||
const changeMind = () => { | ||
s.setMind((s.mind.value + 1) % 2); | ||
}; | ||
|
||
return { | ||
...sharedRef, | ||
mindDescription, | ||
changeMind | ||
}; | ||
} | ||
}; | ||
</script> | ||
``` | ||
## refShared | ||
Short version, this uses [useSharedRef](#usesharedref) in HIVE mode. | ||
### Parameters | ||
```js | ||
import { refShared } from "vue-composable"; | ||
|
||
const refShared = refShared(defaultValue?, id?); | ||
``` | ||
| Parameters | Type | Required | Default | Description | | ||
| ------------ | -------- | -------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| defaultValue | `T` | false | `undefined` | Default value | | ||
| id | `String` | `false` | `undefined` | BroadcastChannel name, if not provided it will get be default `getCurrentInstance().$vnode.tag` this will allow have 1 shared reference per component | | ||
### State | ||
The `refShared` function returns a reactive `ref` variable: | ||
```js | ||
import { refShared } from "vue-composable"; | ||
|
||
const myShared = useSharedRef(); | ||
``` | ||
### Example | ||
<ClientOnly> | ||
<ref-shared-example/> | ||
</ClientOnly> | ||
#### Code | ||
### Code | ||
```vue | ||
<template> | ||
<div> | ||
<p> | ||
To test please open 2 or more tabs and edit the input box | ||
</p> | ||
<input v-model="myRefVar" /> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { refShared } from "vue-composable"; | ||
export default { | ||
name: "ref-shared-example", | ||
setup() { | ||
const myRefVar = refShared("Hello world"); | ||
return { myRefVar }; | ||
} | ||
}; | ||
</script> | ||
``` |
Oops, something went wrong.