support emoji reactions #12

peertube
Namekuji 2023-01-13 10:01:02 -05:00
rodzic 7545855b00
commit 1f084e12bc
5 zmienionych plików z 165 dodań i 16 usunięć

Wyświetl plik

@ -29,7 +29,7 @@
{% end %}
</head>
<body>
<div id="app" data-version='0.1.0-alpha'></div>
<div id="app" data-version='0.1.0-alpha2'></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

53
audon-fe/package-lock.json wygenerowano
Wyświetl plik

@ -1,14 +1,15 @@
{
"name": "audon-fe",
"version": "0.1.0-dev",
"version": "0.1.0-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audon-fe",
"version": "0.1.0-dev",
"version": "0.1.0-alpha",
"dependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1",
"@picmo/popup-picker": "^5.7.2",
"@uriopass/nosleep.js": "^0.12.2",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
@ -18,6 +19,7 @@
"lodash-es": "^4.17.21",
"luxon": "^3.1.1",
"masto": "^4.9.0",
"picmo": "^5.7.2",
"pinia": "^2.0.26",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
@ -100,6 +102,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@floating-ui/core": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.1.0.tgz",
"integrity": "sha512-zbsLwtnHo84w1Kc8rScAo5GMk1GdecSlrflIbfnEBJwvTSj1SL6kkOYV+nHraMCPEy+RNZZUaZyL8JosDGCtGQ=="
},
"node_modules/@floating-ui/dom": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.1.0.tgz",
"integrity": "sha512-TSogMPVxbRe77QCj1dt8NmRiJasPvuc+eT5jnJ6YpLqgOD2zXc5UA3S1qwybN+GVCDNdKfpKy1oj8RpzLJvh6A==",
"dependencies": {
"@floating-ui/core": "^1.0.5"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@ -331,6 +346,20 @@
"node": ">= 8"
}
},
"node_modules/@picmo/popup-picker": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/@picmo/popup-picker/-/popup-picker-5.7.2.tgz",
"integrity": "sha512-AORn2fsYph2NSHZaXViIhDhr0KE2QBjDQnX02TU78Jt76GWxWVV17Y7Kv9aKTO4K7lMSuCCMnq6cZnz+zjgxOQ==",
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/joeattardi"
},
"peerDependencies": {
"picmo": "^5.7.0"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -1103,6 +1132,15 @@
"tslib": "^2.0.3"
}
},
"node_modules/emojibase": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/emojibase/-/emojibase-6.1.0.tgz",
"integrity": "sha512-1GkKJPXP6tVkYJHOBSJHoGOr/6uaDxZ9xJ6H7m6PfdGXTmQgbALHLWaVRY4Gi/qf5x/gT/NUXLPuSHYLqtLtrQ==",
"funding": {
"type": "ko-fi",
"url": "https://ko-fi.com/milesjohnson"
}
},
"node_modules/esbuild": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
@ -2622,6 +2660,17 @@
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.0.0.tgz",
"integrity": "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg=="
},
"node_modules/picmo": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/picmo/-/picmo-5.7.2.tgz",
"integrity": "sha512-A7c5O8x1Xwq11KBYFY93+GIbHnw9PVz35HaWWHn/dgT08GA67M6cXKjjwzLnEAyXSdxXKrEk8/gPyTs+ibzWfQ==",
"dependencies": {
"emojibase": "^6.1.0"
},
"funding": {
"url": "https://github.com/sponsors/joeattardi"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "audon-fe",
"version": "0.1.0-alpha",
"version": "0.1.0-alpha2",
"private": true,
"scripts": {
"dev": "cp -v index.dev.html index.html && vite",
@ -10,6 +10,7 @@
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1",
"@picmo/popup-picker": "^5.7.2",
"@uriopass/nosleep.js": "^0.12.2",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
@ -19,6 +20,7 @@
"lodash-es": "^4.17.21",
"luxon": "^3.1.1",
"masto": "^4.9.0",
"picmo": "^5.7.2",
"pinia": "^2.0.26",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",

Wyświetl plik

@ -2,19 +2,24 @@
import { mdiMicrophone, mdiMicrophoneOff } from "@mdi/js";
import { webfinger } from "../assets/utils";
export default {
setup() {
return {
mdiMicrophone,
mdiMicrophoneOff,
webfinger,
};
},
props: {
talking: Boolean,
type: String,
data: Object,
muted: Boolean,
},
data() {
return {
mdiMicrophone,
mdiMicrophoneOff,
};
emoji: String,
},
computed: {
showEmoji() {
return this.emoji !== undefined;
},
canSpeak() {
return (
this.type === "host" ||
@ -47,9 +52,6 @@ export default {
}
},
},
methods: {
webfinger,
},
};
</script>
@ -62,6 +64,17 @@ export default {
:color="badgeProps.colour"
>
<v-avatar :class="{ rounded: true, talk: talking }" size="70">
<v-overlay
v-model="showEmoji"
contained
persistent
scroll-strategy="none"
no-click-animation
scrim="#000000"
class="align-center justify-center reaction"
>
<span>{{ emoji }}</span>
</v-overlay>
<v-img :src="data?.avatar"></v-img>
</v-avatar>
</v-badge>
@ -70,6 +83,17 @@ export default {
:class="{ rounded: true, talk: talking, 'mt-2': true }"
size="70"
>
<v-overlay
v-model="showEmoji"
contained
persistent
scroll-strategy="none"
no-click-animation
scrim="#000000"
class="align-center justify-center reaction"
>
<span>{{ emoji }}</span>
</v-overlay>
<v-img :src="data?.avatar"></v-img>
</v-avatar>
<h4 :class="canSpeak ? 'mt-1' : 'mt-2'">
@ -88,4 +112,10 @@ export default {
.talk {
outline: 3px solid cornflowerblue;
}
.reaction span {
font-size: 2rem;
color: white;
text-align: center;
}
</style>

Wyświetl plik

@ -3,12 +3,13 @@ import axios from "axios";
import { pushNotFound, webfinger } from "../assets/utils";
import { useMastodonStore } from "../stores/mastodon";
import { map, some, omit, filter, trim, clone } from "lodash-es";
import { darkTheme } from "picmo";
import { createPopup } from "@picmo/popup-picker";
import Participant from "../components/Participant.vue";
import {
mdiMicrophone,
mdiMicrophoneOff,
mdiMicrophoneQuestion,
mdiDoorClosed,
mdiVolumeOff,
mdiClose,
mdiCheck,
@ -16,6 +17,7 @@ import {
mdiLogout,
mdiDotsVertical,
mdiPencil,
mdiEmoticon,
} from "@mdi/js";
import {
Room,
@ -68,10 +70,13 @@ export default {
mdiCheck,
mdiDotsVertical,
mdiPencil,
mdiEmoticon,
v$: useVuelidate(),
donStore: useMastodonStore(),
decoder: new TextDecoder(),
encoder: new TextEncoder(),
roomClient: new Room(),
emojiPicker: null,
};
},
components: {
@ -98,7 +103,6 @@ export default {
roomID: this.$route.params.id,
loading: true,
mainHeight: 700,
roomClient: new Room(),
roomInfo: {
title: this.$t("connecting"),
description: "",
@ -122,6 +126,7 @@ export default {
{ title: this.$t("form.relationships.private"), value: "private" },
],
participants: {},
emojiReactions: {},
cachedMastoData: {},
activeSpeakerIDs: new Set(),
mutedSpeakerIDs: new Set(),
@ -315,11 +320,15 @@ export default {
{ "kind": "speak_request" }
{ "kind": "chat", "data": "..." }
{ "kind": "request_declined", "audon_id": "..."}
{ "kind": "emoji", "emoji": "..." }
*/
const strData = self.decoder.decode(payload);
const jsonData = JSON.parse(strData);
const metadata = JSON.parse(participant.metadata);
switch (jsonData?.kind) {
case "emoji":
self.addEmojiReaction(participant.identity, jsonData.emoji);
break;
case "speak_request": // someone is wanting to be a speaker
self.onSpeakRequestReceived(participant);
break;
@ -496,6 +505,53 @@ export default {
this.showRequestedNotification = true;
}
},
onPickerPopup() {
const btn = document.getElementById("pickerButton");
if (!this.emojiPicker) {
const picker = createPopup(
{
theme: darkTheme,
emojiSize: "1.8rem",
autoFocus: "none",
},
{
referenceElement: btn,
triggerElement: btn,
position: "top",
hideOnEmojiSelect: true,
}
);
const self = this;
picker.addEventListener("emoji:select", ({ emoji }) => {
self.onEmojiSelected(emoji);
});
this.emojiPicker = picker;
}
this.emojiPicker.open();
},
async onEmojiSelected(emoji) {
this.showEmojiMenu = false;
const data = { kind: "emoji", emoji };
const payload = this.encoder.encode(JSON.stringify(data));
await this.roomClient.localParticipant.publishData(
payload,
DataPacket_Kind.RELIABLE
);
this.addEmojiReaction(this.roomClient.localParticipant.identity, emoji);
},
addEmojiReaction(identity, emoji) {
const self = this;
if (self.emojiReactions[identity]) {
clearTimeout(self.emojiReactions[identity].timeoutID);
}
const timeoutID = setTimeout(() => {
self.emojiReactions = omit(self.emojiReactions, identity);
}, 5000);
self.emojiReactions[identity] = {
timeoutID,
emoji,
};
},
async publishDataToHostAndCohosts(data) {
const payload = this.encoder.encode(JSON.stringify(data));
// participants - speakers
@ -807,6 +863,7 @@ export default {
type="host"
:data="cachedMastoData[key]"
:muted="mutedSpeakerIDs.has(key)"
:emoji="emojiReactions[key]?.emoji"
></Participant>
<Participant
v-if="isCohost(value)"
@ -814,6 +871,7 @@ export default {
type="cohost"
:data="cachedMastoData[key]"
:muted="mutedSpeakerIDs.has(key)"
:emoji="emojiReactions[key]?.emoji"
></Participant>
<Participant
v-if="isSpeaker(key)"
@ -821,6 +879,7 @@ export default {
type="speaker"
:data="cachedMastoData[key]"
:muted="mutedSpeakerIDs.has(key)"
:emoji="emojiReactions[key]?.emoji"
>
</Participant>
</template>
@ -831,6 +890,7 @@ export default {
v-if="!isHost(key) && !isCohost(value) && !isSpeaker(key)"
:data="cachedMastoData[key]"
type="listener"
:emoji="emojiReactions[key]?.emoji"
></Participant>
</template>
</v-row>
@ -845,7 +905,15 @@ export default {
>{{ $t("enterRoom") }}</v-btn
>
</v-card-actions>
<v-card-actions v-else class="justify-center" style="gap: 50px">
<v-card-actions v-else class="justify-center" style="gap: 20px">
<v-btn
:icon="mdiEmoticon"
color="white"
variant="flat"
@click="onPickerPopup"
id="pickerButton"
>
</v-btn>
<v-btn
:icon="micStatusIcon"
color="white"