more progress on refactor
This commit is contained in:
parent
8ba1b68a21
commit
157ee2e1fb
0
client/ABOUT.md
Normal file
0
client/ABOUT.md
Normal file
@ -21,7 +21,6 @@
|
|||||||
"@fortawesome/fontawesome-free": "^5.12.0",
|
"@fortawesome/fontawesome-free": "^5.12.0",
|
||||||
"animejs": "^3.1.0",
|
"animejs": "^3.1.0",
|
||||||
"axios": "^0.19.1",
|
"axios": "^0.19.1",
|
||||||
"bulma": "^0.8.0",
|
|
||||||
"date-fns": "^2.9.0",
|
"date-fns": "^2.9.0",
|
||||||
"eventemitter3": "^4.0.0",
|
"eventemitter3": "^4.0.0",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
@ -34,20 +33,17 @@
|
|||||||
"vue-context": "^5.0.0",
|
"vue-context": "^5.0.0",
|
||||||
"vue-notification": "^1.3.20",
|
"vue-notification": "^1.3.20",
|
||||||
"vue-property-decorator": "^8.3.0",
|
"vue-property-decorator": "^8.3.0",
|
||||||
"vue-router": "^3.1.5",
|
|
||||||
"vuex": "^3.1.2"
|
"vuex": "^3.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/animejs": "^3.1.0",
|
"@types/animejs": "^3.1.0",
|
||||||
"@types/vue": "^2.0.0",
|
"@types/vue": "^2.0.0",
|
||||||
"@types/w3c-image-capture": "^1.0.2",
|
|
||||||
"@vue/cli-plugin-eslint": "^4.1.0",
|
"@vue/cli-plugin-eslint": "^4.1.0",
|
||||||
"@vue/cli-plugin-typescript": "^4.1.0",
|
"@vue/cli-plugin-typescript": "^4.1.0",
|
||||||
"@vue/cli-plugin-vuex": "^4.1.0",
|
"@vue/cli-plugin-vuex": "^4.1.0",
|
||||||
"@vue/cli-service": "^4.1.0",
|
"@vue/cli-service": "^4.1.0",
|
||||||
"@vue/eslint-config-prettier": "^5.0.0",
|
"@vue/eslint-config-prettier": "^5.0.0",
|
||||||
"@vue/eslint-config-typescript": "^4.0.0",
|
"@vue/eslint-config-typescript": "^4.0.0",
|
||||||
"autoprefixer": "^9.7.4",
|
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-prettier": "^3.1.1",
|
"eslint-plugin-prettier": "^3.1.1",
|
||||||
"eslint-plugin-vue": "^5.0.0",
|
"eslint-plugin-vue": "^5.0.0",
|
||||||
|
BIN
client/public/chat.mp3
Normal file
BIN
client/public/chat.mp3
Normal file
Binary file not shown.
@ -18,6 +18,5 @@
|
|||||||
<strong>We're sorry but test doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but test doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="neko"></div>
|
<div id="neko"></div>
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,11 +2,7 @@
|
|||||||
<div id="neko">
|
<div id="neko">
|
||||||
<main class="neko-main">
|
<main class="neko-main">
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<div class="neko">
|
<neko-header />
|
||||||
<img src="@/assets/logo.svg" alt="n.eko" />
|
|
||||||
<span><b>n</b>.eko</span>
|
|
||||||
</div>
|
|
||||||
<i class="fas fa-bars toggle" @click="toggle" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<neko-video ref="video" />
|
<neko-video ref="video" />
|
||||||
@ -20,8 +16,8 @@
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<neko-controls />
|
<neko-controls />
|
||||||
</div>
|
</div>
|
||||||
<div class="emoji">
|
<div class="emotes">
|
||||||
<neko-emoji />
|
<neko-emotes />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -29,7 +25,7 @@
|
|||||||
<neko-side v-if="side" />
|
<neko-side v-if="side" />
|
||||||
<neko-connect v-if="!connected" />
|
<neko-connect v-if="!connected" />
|
||||||
<neko-about v-if="about" />
|
<neko-about v-if="about" />
|
||||||
<notifications group="neko" position="top left" />
|
<notifications group="neko" position="top left" style="top: 50px;" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -55,44 +51,6 @@
|
|||||||
height: $menu-height;
|
height: $menu-height;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.toggle {
|
|
||||||
display: block;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 32px;
|
|
||||||
background: $background-primary;
|
|
||||||
justify-self: flex-end;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin: 5px 10px 0 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.neko {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
width: 150px;
|
|
||||||
margin-left: 20px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
height: 30px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
|
|
||||||
b {
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container {
|
.video-container {
|
||||||
@ -130,7 +88,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji {
|
.emotes {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -152,8 +110,9 @@
|
|||||||
import Side from '~/components/side.vue'
|
import Side from '~/components/side.vue'
|
||||||
import Controls from '~/components/controls.vue'
|
import Controls from '~/components/controls.vue'
|
||||||
import Members from '~/components/members.vue'
|
import Members from '~/components/members.vue'
|
||||||
import Emoji from '~/components/emoji.vue'
|
import Emotes from '~/components/emotes.vue'
|
||||||
import About from '~/components/about.vue'
|
import About from '~/components/about.vue'
|
||||||
|
import Header from '~/components/header.vue'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'neko',
|
name: 'neko',
|
||||||
@ -164,8 +123,9 @@
|
|||||||
'neko-side': Side,
|
'neko-side': Side,
|
||||||
'neko-controls': Controls,
|
'neko-controls': Controls,
|
||||||
'neko-members': Members,
|
'neko-members': Members,
|
||||||
'neko-emoji': Emoji,
|
'neko-emotes': Emotes,
|
||||||
'neko-about': About,
|
'neko-about': About,
|
||||||
|
'neko-header': Header,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
@ -182,9 +142,5 @@
|
|||||||
get connected() {
|
get connected() {
|
||||||
return this.$accessor.connected
|
return this.$accessor.connected
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
|
||||||
this.$accessor.client.toggleSide()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
@import "vendor/swal";
|
@import "vendor/swal";
|
||||||
@import "vendor/tooltip";
|
@import "vendor/tooltip";
|
||||||
@import "vendor/github";
|
@import "vendor/github";
|
||||||
// @import "vendor/bulma";
|
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
-webkit-font-smoothing: subpixel-antialiased;
|
-webkit-font-smoothing: subpixel-antialiased;
|
||||||
|
245
client/src/assets/styles/vendor/_bulma.scss
vendored
245
client/src/assets/styles/vendor/_bulma.scss
vendored
@ -1,245 +0,0 @@
|
|||||||
@import "~bulma/sass/utilities/initial-variables.sass";
|
|
||||||
@import "~bulma/sass/utilities/functions.sass";
|
|
||||||
@import "~bulma/sass/utilities/derived-variables.sass";
|
|
||||||
@import "~bulma/sass/utilities/animations.sass";
|
|
||||||
@import "~bulma/sass/utilities/mixins.sass";
|
|
||||||
@import "~bulma/sass/utilities/controls.sass";
|
|
||||||
|
|
||||||
// $black: hsl(0, 0%, 4%);
|
|
||||||
// $black-bis: hsl(0, 0%, 7%);
|
|
||||||
// $black-ter: hsl(0, 0%, 14%);
|
|
||||||
|
|
||||||
// $grey-darker: hsl(0, 0%, 21%);
|
|
||||||
// $grey-dark: hsl(0, 0%, 29%);
|
|
||||||
// $grey: hsl(0, 0%, 48%);
|
|
||||||
// $grey-light: hsl(0, 0%, 71%);
|
|
||||||
// $grey-lighter: hsl(0, 0%, 86%);
|
|
||||||
// $grey-lightest: hsl(0, 0%, 93%);
|
|
||||||
|
|
||||||
// $white-ter: hsl(0, 0%, 96%);
|
|
||||||
// $white-bis: hsl(0, 0%, 98%);
|
|
||||||
// $white: hsl(0, 0%, 100%);
|
|
||||||
|
|
||||||
// $orange: hsl(14, 100%, 53%);
|
|
||||||
// $yellow: hsl(48, 100%, 67%);
|
|
||||||
// $green: hsl(141, 53%, 53%);
|
|
||||||
// $turquoise: hsl(171, 100%, 41%);
|
|
||||||
// $cyan: hsl(204, 71%, 53%);
|
|
||||||
// $blue: hsl(217, 71%, 53%);
|
|
||||||
// $purple: hsl(271, 100%, 71%);
|
|
||||||
// $red: hsl(348, 86%, 61%);
|
|
||||||
|
|
||||||
// Typography
|
|
||||||
$family-sans-serif: $text-family;
|
|
||||||
// $family-monospace: monospace;
|
|
||||||
// $render-mode: optimizeLegibility;
|
|
||||||
|
|
||||||
// $size-1: 3rem;
|
|
||||||
// $size-2: 2.5rem;
|
|
||||||
// $size-3: 2rem;
|
|
||||||
// $size-4: 1.5rem;
|
|
||||||
// $size-5: 1.25rem;
|
|
||||||
// $size-6: 1rem;
|
|
||||||
// $size-7: 0.75rem;
|
|
||||||
|
|
||||||
// $weight-light: 300;
|
|
||||||
// $weight-normal: 400;
|
|
||||||
// $weight-medium: 500;
|
|
||||||
// $weight-semibold: 600;
|
|
||||||
// $weight-bold: 700;
|
|
||||||
|
|
||||||
// Spacing
|
|
||||||
// $block-spacing: 1.5rem;
|
|
||||||
|
|
||||||
// Responsiveness
|
|
||||||
// The container horizontal gap, which acts as the offset for breakpoints
|
|
||||||
// $gap: 32px;
|
|
||||||
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
|
|
||||||
// $tablet: 769px;
|
|
||||||
// 960px container + 4rem
|
|
||||||
// $desktop: 960px + (2 * // $gap);
|
|
||||||
// 1152px container + 4rem
|
|
||||||
// $widescreen: 1152px + (2 * // $gap);
|
|
||||||
// $widescreen-enabled: true;
|
|
||||||
// 1344px container + 4rem
|
|
||||||
// $fullhd: 1344px + (2 * // $gap);
|
|
||||||
// $fullhd-enabled: true;
|
|
||||||
|
|
||||||
// Miscellaneous
|
|
||||||
// $easing: ease-out;
|
|
||||||
// $radius-small: 2px;
|
|
||||||
// $radius: 4px;
|
|
||||||
// $radius-large: 6px;
|
|
||||||
// $radius-rounded: 290486px;
|
|
||||||
// $speed: 86ms;
|
|
||||||
|
|
||||||
// Flags
|
|
||||||
// $variable-columns: true;
|
|
||||||
|
|
||||||
// $primary: $turquoise;
|
|
||||||
// $info: $cyan;
|
|
||||||
// $success: $green;
|
|
||||||
// $warning: $yellow;
|
|
||||||
// $danger: $red;
|
|
||||||
|
|
||||||
// $light: $white-ter;
|
|
||||||
// $dark: $grey-darker;
|
|
||||||
|
|
||||||
// Invert colors
|
|
||||||
// $orange-invert: findColorInvert($orange);
|
|
||||||
// $yellow-invert: findColorInvert($yellow);
|
|
||||||
// $green-invert: findColorInvert($green);
|
|
||||||
// $turquoise-invert: findColorInvert($turquoise);
|
|
||||||
// $cyan-invert: findColorInvert($cyan);
|
|
||||||
// $blue-invert: findColorInvert($blue);
|
|
||||||
// $purple-invert: findColorInvert($purple);
|
|
||||||
// $red-invert: findColorInvert($red);
|
|
||||||
|
|
||||||
// $primary-invert: findColorInvert($primary);
|
|
||||||
// $primary-light: findLightColor($primary);
|
|
||||||
// $primary-dark: findDarkColor($primary);
|
|
||||||
// $info-invert: findColorInvert($info);
|
|
||||||
// $info-light: findLightColor($info);
|
|
||||||
// $info-dark: findDarkColor($info);
|
|
||||||
// $success-invert: findColorInvert($success);
|
|
||||||
// $success-light: findLightColor($success);
|
|
||||||
// $success-dark: findDarkColor($success);
|
|
||||||
// $warning-invert: findColorInvert($warning);
|
|
||||||
// $warning-light: findLightColor($warning);
|
|
||||||
// $warning-dark: findDarkColor($warning);
|
|
||||||
// $danger-invert: findColorInvert($danger);
|
|
||||||
// $danger-light: findLightColor($danger);
|
|
||||||
// $danger-dark: findDarkColor($danger);
|
|
||||||
// $light-invert: findColorInvert($light);
|
|
||||||
// $dark-invert: findColorInvert($dark);
|
|
||||||
|
|
||||||
// General colors
|
|
||||||
// $scheme-main: $white;
|
|
||||||
// $scheme-main-bis: $white-bis;
|
|
||||||
// $scheme-main-ter: $white-ter;
|
|
||||||
// $scheme-invert: $black;
|
|
||||||
// $scheme-invert-bis: $black-bis;
|
|
||||||
// $scheme-invert-ter: $black-ter;
|
|
||||||
|
|
||||||
// $background: $white-ter;
|
|
||||||
$border: $grey-light;
|
|
||||||
$border-hover: $grey;
|
|
||||||
// $border-light: $grey-lightest;
|
|
||||||
// $border-light-hover: $grey-light;
|
|
||||||
|
|
||||||
// Text colors
|
|
||||||
// $text: $grey-dark;
|
|
||||||
// $text-invert: findColorInvert($text);
|
|
||||||
// $text-light: $grey;
|
|
||||||
// $text-strong: $grey-darker;
|
|
||||||
|
|
||||||
// Code colors
|
|
||||||
// $code: $red;
|
|
||||||
// $code-background: $background;
|
|
||||||
|
|
||||||
// $pre: $text;
|
|
||||||
// $pre-background: $background;
|
|
||||||
|
|
||||||
// Link colors
|
|
||||||
// $link: $blue;
|
|
||||||
// $link-invert: findColorInvert($link);
|
|
||||||
// $link-light: findLightColor($link);
|
|
||||||
// $link-dark: findDarkColor($link);
|
|
||||||
// $link-visited: $purple;
|
|
||||||
|
|
||||||
// $link-hover: $grey-darker;
|
|
||||||
// $link-hover-border: $grey-light;
|
|
||||||
|
|
||||||
// $link-focus: $grey-darker;
|
|
||||||
// $link-focus-border: $blue;
|
|
||||||
|
|
||||||
// $link-active: $grey-darker;
|
|
||||||
// $link-active-border: $grey-dark;
|
|
||||||
|
|
||||||
// Typography
|
|
||||||
// $family-primary: $family-sans-serif;
|
|
||||||
// $family-secondary: $family-sans-serif;
|
|
||||||
// $family-code: $family-monospace;
|
|
||||||
|
|
||||||
// $size-small: $size-7;
|
|
||||||
// $size-normal: $size-6;
|
|
||||||
// $size-medium: $size-5;
|
|
||||||
// $size-large: $size-4;
|
|
||||||
|
|
||||||
// Lists and maps
|
|
||||||
// $custom-colors: null;
|
|
||||||
// $custom-shades: null;
|
|
||||||
|
|
||||||
// $colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert, $primary-light, $primary-dark), "link": ($link, $link-invert, $link-light, $link-dark), "info": ($info, $info-invert, $info-light, $info-dark), "success": ($success, $success-invert, $success-light, $success-dark), "warning": ($warning, $warning-invert, $warning-light, $warning-dark), "danger": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors);
|
|
||||||
// $shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades);
|
|
||||||
// $sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7;
|
|
||||||
|
|
||||||
@import "~bulma/sass/base/minireset";
|
|
||||||
@import "~bulma/sass/base/generic";
|
|
||||||
@import "~bulma/sass/base/helpers";
|
|
||||||
|
|
||||||
@import "~bulma/sass/elements/box.sass";
|
|
||||||
@import "~bulma/sass/elements/button.sass";
|
|
||||||
@import "~bulma/sass/elements/container.sass";
|
|
||||||
@import "~bulma/sass/elements/content.sass";
|
|
||||||
@import "~bulma/sass/elements/icon.sass";
|
|
||||||
@import "~bulma/sass/elements/image.sass";
|
|
||||||
@import "~bulma/sass/elements/notification.sass";
|
|
||||||
@import "~bulma/sass/elements/progress.sass";
|
|
||||||
@import "~bulma/sass/elements/table.sass";
|
|
||||||
@import "~bulma/sass/elements/tag.sass";
|
|
||||||
@import "~bulma/sass/elements/title.sass";
|
|
||||||
@import "~bulma/sass/elements/other.sass";
|
|
||||||
|
|
||||||
// $input-color: $text-strong;
|
|
||||||
// $input-background-color: $scheme-main;
|
|
||||||
// $input-border-color: $border;
|
|
||||||
// $input-height: $control-height;
|
|
||||||
// $input-shadow: inset 0 0.0625em 0.125em rgba($scheme-invert, 0.05);
|
|
||||||
// $input-placeholder-color: rgba($input-color, 0.3);
|
|
||||||
// $input-hover-color: $text-strong;
|
|
||||||
// $input-hover-border-color: $border-hover;
|
|
||||||
// $input-focus-color: $text-strong;
|
|
||||||
// $input-focus-border-color: $link;
|
|
||||||
// $input-focus-box-shadow-size: 0 0 0 0.125em;
|
|
||||||
// $input-focus-box-shadow-color: rgba($link, 0.25);
|
|
||||||
// $input-disabled-color: $text-light;
|
|
||||||
// $input-disabled-background-color: $background;
|
|
||||||
// $input-disabled-border-color: $background;
|
|
||||||
// $input-disabled-placeholder-color: rgba($input-disabled-color, 0.3);
|
|
||||||
// $input-arrow: $link;
|
|
||||||
// $input-icon-color: $border;
|
|
||||||
// $input-icon-active-color: $text;
|
|
||||||
// $input-radius: $radius;
|
|
||||||
|
|
||||||
@import "~bulma/sass/form/shared.sass";
|
|
||||||
|
|
||||||
// $textarea-padding: $control-padding-horizontal;
|
|
||||||
// $textarea-max-height: 40em;
|
|
||||||
// $textarea-min-height: 8em;
|
|
||||||
@import "~bulma/sass/form/input-textarea.sass";
|
|
||||||
@import "~bulma/sass/form/checkbox-radio.sass";
|
|
||||||
@import "~bulma/sass/form/select.sass";
|
|
||||||
@import "~bulma/sass/form/file.sass";
|
|
||||||
@import "~bulma/sass/form/tools.sass";
|
|
||||||
|
|
||||||
// @import "~bulma/sass/components/breadcrumb.sass";
|
|
||||||
// @import "~bulma/sass/components/card.sass";
|
|
||||||
// @import "~bulma/sass/components/dropdown.sass";
|
|
||||||
// @import "~bulma/sass/components/level.sass";
|
|
||||||
// @import "~bulma/sass/components/list.sass";
|
|
||||||
// @import "~bulma/sass/components/media.sass";
|
|
||||||
// @import "~bulma/sass/components/menu.sass";
|
|
||||||
// @import "~bulma/sass/components/message.sass";
|
|
||||||
// @import "~bulma/sass/components/modal.sass";
|
|
||||||
// @import "~bulma/sass/components/navbar.sass";
|
|
||||||
// @import "~bulma/sass/components/pagination.sass";
|
|
||||||
// @import "~bulma/sass/components/panel.sass";
|
|
||||||
@import "~bulma/sass/components/tabs.sass";
|
|
||||||
|
|
||||||
@import "~bulma/sass/grid/columns.sass";
|
|
||||||
@import "~bulma/sass/grid/tiles.sass";
|
|
||||||
|
|
||||||
// @import "~bulma/sass/layout/hero.sass";
|
|
||||||
// @import "~bulma/sass/layout/section.sass";
|
|
||||||
// @import "~bulma/sass/layout/footer.sass";
|
|
2
client/src/assets/styles/vendor/_swal.scss
vendored
2
client/src/assets/styles/vendor/_swal.scss
vendored
@ -152,7 +152,7 @@ $swal2-confirm-button-font-size: 1.0625em;
|
|||||||
// CANCEL BUTTON
|
// CANCEL BUTTON
|
||||||
$swal2-cancel-button-border: 0;
|
$swal2-cancel-button-border: 0;
|
||||||
$swal2-cancel-button-border-radius: .25em;
|
$swal2-cancel-button-border-radius: .25em;
|
||||||
$swal2-cancel-button-background-color: #aaa;
|
$swal2-cancel-button-background-color: $background-floating;
|
||||||
$swal2-cancel-button-color: $swal2-white;
|
$swal2-cancel-button-color: $swal2-white;
|
||||||
$swal2-cancel-button-font-size: 1.0625em;
|
$swal2-cancel-button-font-size: 1.0625em;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="about" @click.stop.prevent="toggle">
|
<div class="about" @click="toggle">
|
||||||
<div class="window">
|
<div class="window">
|
||||||
<div class="loading" v-if="loading">
|
<div class="loading" v-if="loading">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
@ -147,7 +147,7 @@
|
|||||||
if (this.about === '') {
|
if (this.about === '') {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$http
|
this.$http
|
||||||
.get<string>('https://raw.githubusercontent.com/nurdism/neko/master/README.md')
|
.get<string>('https://raw.githubusercontent.com/nurdism/neko/master/client/ABOUT.md')
|
||||||
.then(res => {
|
.then(res => {
|
||||||
return this.$http.post('https://api.github.com/markdown', {
|
return this.$http.post('https://api.github.com/markdown', {
|
||||||
text: res.data,
|
text: res.data,
|
||||||
@ -163,8 +163,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle(event: { target?: HTMLElement }) {
|
||||||
this.$accessor.client.toggleAbout()
|
if (event.target && event.target.classList.contains('about')) {
|
||||||
|
this.$accessor.client.toggleAbout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ul class="chat-history" ref="history" @click="onClick">
|
<ul class="chat-history" ref="history" @click="onClick">
|
||||||
<template v-for="(message, index) in history">
|
<template v-for="(message, index) in history">
|
||||||
<li :key="index" class="message" v-if="message.type === 'text'">
|
<li :key="index" class="message" v-if="message.type === 'text'">
|
||||||
<div class="author">
|
<div class="author" @contextmenu.stop.prevent="onContext($event, { member: member(message.id) })">
|
||||||
<img :src="`https://api.adorable.io/avatars/40/${member(message.id).username}.png`" />
|
<img :src="`https://api.adorable.io/avatars/40/${member(message.id).username}.png`" />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -19,22 +19,24 @@
|
|||||||
<li :key="index" class="event" v-if="message.type === 'event'">
|
<li :key="index" class="event" v-if="message.type === 'event'">
|
||||||
<span
|
<span
|
||||||
v-tooltip="{
|
v-tooltip="{
|
||||||
content: `${member(message.id).username} ${message.content}`,
|
content: `${timestamp(message.created)}, ${member(message.id).username} ${message.content}`,
|
||||||
placement: 'left',
|
placement: 'left',
|
||||||
offset: 3,
|
offset: 3,
|
||||||
boundariesElement: 'body',
|
boundariesElement: 'body',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<strong>{{ member(message.id).username }}</strong>
|
<strong v-if="message.id === id">You</strong>
|
||||||
|
<strong v-else>{{ member(message.id).username }}</strong>
|
||||||
{{ message.content }}
|
{{ message.content }}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="chat-send">
|
<neko-context ref="context" />
|
||||||
|
<div v-if="!muted" class="chat-send">
|
||||||
<div class="accent" />
|
<div class="accent" />
|
||||||
<div class="text-container">
|
<div class="text-container">
|
||||||
<textarea placeholder="Send a message" @keydown="onKeyDown" v-model="content" />
|
<textarea ref="chat" placeholder="Send a message" @keydown="onKeyDown" v-model="content" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -112,6 +114,8 @@
|
|||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
|
|
||||||
.content-head {
|
.content-head {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
color: $text-normal;
|
color: $text-normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -213,6 +217,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
color: $text-muted;
|
color: $text-muted;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -305,6 +310,7 @@
|
|||||||
import { formatRelative } from 'date-fns'
|
import { formatRelative } from 'date-fns'
|
||||||
|
|
||||||
import Markdown from './markdown'
|
import Markdown from './markdown'
|
||||||
|
import Content from './context.vue'
|
||||||
|
|
||||||
const length = 512 // max length of message
|
const length = 512 // max length of message
|
||||||
|
|
||||||
@ -312,10 +318,12 @@
|
|||||||
name: 'neko-chat',
|
name: 'neko-chat',
|
||||||
components: {
|
components: {
|
||||||
'neko-markdown': Markdown,
|
'neko-markdown': Markdown,
|
||||||
|
'neko-context': Content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
@Ref('history') readonly _history!: HTMLElement
|
@Ref('history') readonly _history!: HTMLElement
|
||||||
|
@Ref('context') readonly _context!: any
|
||||||
|
|
||||||
_content = ''
|
_content = ''
|
||||||
|
|
||||||
@ -323,6 +331,10 @@
|
|||||||
return this.$accessor.user.id
|
return this.$accessor.user.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get muted() {
|
||||||
|
return this.$accessor.user.muted
|
||||||
|
}
|
||||||
|
|
||||||
get history() {
|
get history() {
|
||||||
return this.$accessor.chat.history
|
return this.$accessor.chat.history
|
||||||
}
|
}
|
||||||
@ -346,6 +358,13 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch('muted')
|
||||||
|
onMutedChange(muted: boolean) {
|
||||||
|
if (muted) {
|
||||||
|
this._content = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this._history.scrollTop = this._history.scrollHeight
|
this._history.scrollTop = this._history.scrollHeight
|
||||||
@ -360,6 +379,10 @@
|
|||||||
return formatRelative(time, new Date())
|
return formatRelative(time, new Date())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onContext(event: MouseEvent, data: any) {
|
||||||
|
this._context.open(event, data)
|
||||||
|
}
|
||||||
|
|
||||||
onClick(event: { target?: HTMLElement; preventDefault(): void }) {
|
onClick(event: { target?: HTMLElement; preventDefault(): void }) {
|
||||||
const { target } = event
|
const { target } = event
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@ -382,7 +405,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event: KeyboardEvent) {
|
onKeyDown(event: KeyboardEvent) {
|
||||||
if (typeof this._content === 'undefined') {
|
if (typeof this._content === 'undefined' || this.muted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<span><b>n</b>.eko</span>
|
<span><b>n</b>.eko</span>
|
||||||
</div>
|
</div>
|
||||||
<form class="message" v-if="!connecting" @submit.stop.prevent="connect">
|
<form class="message" v-if="!connecting" @submit.stop.prevent="connect">
|
||||||
<span>Please enter the password:</span>
|
<span>Please Login</span>
|
||||||
<input type="text" placeholder="Username" v-model="username" />
|
<input type="text" placeholder="Username" v-model="username" />
|
||||||
<input type="password" placeholder="Password" v-model="password" />
|
<input type="password" placeholder="Password" v-model="password" />
|
||||||
<button type="submit" @click.stop.prevent="connect">
|
<button type="submit" @click.stop.prevent="connect">
|
||||||
@ -67,9 +67,10 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: 5px 0;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@ -145,7 +146,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||||
|
import { get, set } from '~/utils/localstorage'
|
||||||
@Component({ name: 'neko-connect' })
|
@Component({ name: 'neko-connect' })
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
private username = ''
|
private username = ''
|
||||||
@ -156,8 +157,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
const { username, password } = this
|
this.$accessor.connect({ username: this.username, password: this.password })
|
||||||
this.$accessor.connect({ username, password })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
244
client/src/components/context.vue
Normal file
244
client/src/components/context.vue
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
<template>
|
||||||
|
<vue-context class="context" ref="context">
|
||||||
|
<template slot-scope="child" v-if="child.data">
|
||||||
|
<li class="header">
|
||||||
|
<div class="user">
|
||||||
|
<img :src="`https://api.adorable.io/avatars/25/${child.data.member.username}.png`" />
|
||||||
|
<strong>{{ child.data.member.username }}</strong>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="seperator" />
|
||||||
|
<li>
|
||||||
|
<span @click="ignore(child.data.member)" v-if="!child.data.member.ignored">Ignore</span>
|
||||||
|
<span @click="unignore(child.data.member)" v-else>Unignore</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<template v-if="admin">
|
||||||
|
<li>
|
||||||
|
<span @click="mute(child.data.member)" v-if="!child.data.member.muted">Mute</span>
|
||||||
|
<span @click="unmute(child.data.member)" v-else>Unmute</span>
|
||||||
|
</li>
|
||||||
|
<li v-if="child.data.member.id === host">
|
||||||
|
<span @click="adminRelease(child.data.member)">Force Release Controls</span>
|
||||||
|
</li>
|
||||||
|
<li v-if="child.data.member.id === host">
|
||||||
|
<span @click="adminControl(child.data.member)">Force Take Controls</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span v-if="child.data.member.id !== host" @click="adminGive(child.data.member)">Give Controls</span>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<li v-if="hosting">
|
||||||
|
<span @click="give(child.data.member)">Give Controls</span>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="admin">
|
||||||
|
<li class="seperator" />
|
||||||
|
<li>
|
||||||
|
<span @click="kick(child.data.member)" style="color: #f04747">Kick</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span @click="ban(child.data.member)" style="color: #f04747">Ban IP</span>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</vue-context>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.context {
|
||||||
|
background-color: $background-floating;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
min-width: 150px;
|
||||||
|
z-index: 1500;
|
||||||
|
position: fixed;
|
||||||
|
list-style: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-height: calc(100% - 50px);
|
||||||
|
overflow-y: auto;
|
||||||
|
color: $interactive-normal;
|
||||||
|
user-select: none;
|
||||||
|
box-shadow: $elevation-high;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
align-content: center;
|
||||||
|
|
||||||
|
&.header {
|
||||||
|
.user {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-content: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
line-height: 25px;
|
||||||
|
font-weight: 700;
|
||||||
|
max-width: 200px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.seperator {
|
||||||
|
height: 1px;
|
||||||
|
background: $background-secondary;
|
||||||
|
margin: 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: 400;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: $background-modifier-hover;
|
||||||
|
color: $interactive-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||||
|
import { Member } from '~/neko/types'
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { VueContext } from 'vue-context'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'neko-context',
|
||||||
|
components: {
|
||||||
|
'vue-context': VueContext,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class extends Vue {
|
||||||
|
@Ref('context') readonly context!: any
|
||||||
|
|
||||||
|
get admin() {
|
||||||
|
return this.$accessor.user.admin
|
||||||
|
}
|
||||||
|
|
||||||
|
get hosting() {
|
||||||
|
return this.$accessor.remote.hosting
|
||||||
|
}
|
||||||
|
|
||||||
|
get host() {
|
||||||
|
return this.$accessor.remote.id
|
||||||
|
}
|
||||||
|
|
||||||
|
open(event: MouseEvent, data: any) {
|
||||||
|
this.context.open(event, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
kick(member: Member) {
|
||||||
|
this.$swal({
|
||||||
|
title: `Kick ${member.username}?`,
|
||||||
|
text: `Are you sure you want to kick ${member.username}?`,
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Yes',
|
||||||
|
}).then(({ value }) => {
|
||||||
|
if (value) {
|
||||||
|
this.$accessor.user.kick(member)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ban(member: Member) {
|
||||||
|
this.$swal({
|
||||||
|
title: `Ban ${member.username}?`,
|
||||||
|
text: `Are you sure you want to ban ${member.username}? You will need to restart the server to undo this.`,
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Yes',
|
||||||
|
}).then(({ value }) => {
|
||||||
|
if (value) {
|
||||||
|
this.$accessor.user.ban(member)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mute(member: Member) {
|
||||||
|
this.$swal({
|
||||||
|
title: `Mute ${member.username}?`,
|
||||||
|
text: `Are you sure you want to mute ${member.username}?`,
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Yes',
|
||||||
|
}).then(({ value }) => {
|
||||||
|
if (value) {
|
||||||
|
this.$accessor.user.mute(member)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unmute(member: Member) {
|
||||||
|
this.$swal({
|
||||||
|
title: `Unmute ${member.username}?`,
|
||||||
|
text: `Are you sure you want to unmute ${member.username}?`,
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Yes',
|
||||||
|
}).then(({ value }) => {
|
||||||
|
if (value) {
|
||||||
|
this.$accessor.user.unmute(member)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
adminRelease(member: Member) {
|
||||||
|
this.$accessor.remote.adminRelease()
|
||||||
|
}
|
||||||
|
|
||||||
|
adminControl(member: Member) {
|
||||||
|
this.$accessor.remote.adminControl()
|
||||||
|
}
|
||||||
|
|
||||||
|
adminGive(member: Member) {
|
||||||
|
this.$accessor.remote.adminGive(member)
|
||||||
|
}
|
||||||
|
|
||||||
|
give(member: Member) {
|
||||||
|
this.$accessor.remote.give(member)
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore(member: Member) {
|
||||||
|
this.$accessor.user.setIgnored({ id: member.id, ignored: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
unignore(member: Member) {
|
||||||
|
this.$accessor.user.setIgnored({ id: member.id, ignored: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
107
client/src/components/emote.vue
Normal file
107
client/src/components/emote.vue
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="emote" @click.stop.prevent="run" class="emote">
|
||||||
|
<i :class="classes"></i>
|
||||||
|
<i :class="classes"></i>
|
||||||
|
<i :class="classes"></i>
|
||||||
|
<i :class="classes"></i>
|
||||||
|
<i :class="classes"></i>
|
||||||
|
<i :class="classes"></i>
|
||||||
|
<i :class="classes"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.emote {
|
||||||
|
width: 150px;
|
||||||
|
height: 30px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
i {
|
||||||
|
position: absolute;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.heart {
|
||||||
|
color: rgb(204, 72, 72);
|
||||||
|
}
|
||||||
|
&.poo {
|
||||||
|
color: rgb(112, 89, 58);
|
||||||
|
}
|
||||||
|
&.grin {
|
||||||
|
color: rgb(228, 194, 84);
|
||||||
|
}
|
||||||
|
&.dizzy {
|
||||||
|
color: rgb(199, 199, 199);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Ref, Vue, Prop } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component({ name: 'neko-emote' })
|
||||||
|
export default class extends Vue {
|
||||||
|
@Prop({
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
id!: string
|
||||||
|
|
||||||
|
@Ref('emote') container!: HTMLElement
|
||||||
|
|
||||||
|
get emote() {
|
||||||
|
return this.$accessor.chat.emotes[this.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
private classes: string[] = []
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const range = 90
|
||||||
|
let count = 0
|
||||||
|
let finish: Array<Promise<any>> = []
|
||||||
|
|
||||||
|
const emotes: any = {
|
||||||
|
heart: 'fa-heart',
|
||||||
|
poo: 'fa-poo',
|
||||||
|
grin: 'fa-grin-tears',
|
||||||
|
ghost: 'fa-ghost',
|
||||||
|
}
|
||||||
|
|
||||||
|
this.classes = ['fas', emotes[this.emote.type] || 'fa-heart', this.emote.type]
|
||||||
|
|
||||||
|
for (let child of this.container.children) {
|
||||||
|
const ele = child as HTMLElement
|
||||||
|
ele.style['left'] = `${count % 2 ? this.$anime.random(0, range) : this.$anime.random(-range, 0)}px`
|
||||||
|
ele.style['opacity'] = `0`
|
||||||
|
|
||||||
|
const animation = this.$anime({
|
||||||
|
targets: child,
|
||||||
|
keyframes: [
|
||||||
|
{ left: count % 2 ? this.$anime.random(0, range) : this.$anime.random(-range, 0), opacity: 1 },
|
||||||
|
{ left: count % 2 ? this.$anime.random(-range, 0) : this.$anime.random(0, range), opacity: 0.5 },
|
||||||
|
{ left: count % 2 ? this.$anime.random(0, range) : this.$anime.random(-range, 0), opacity: 0 },
|
||||||
|
],
|
||||||
|
elasticity: 600,
|
||||||
|
rotate: this.$anime.random(-35, 35),
|
||||||
|
top: this.$anime.random(-100, -250),
|
||||||
|
duration: this.$anime.random(1000, 2000),
|
||||||
|
easing: 'easeInOutQuad',
|
||||||
|
})
|
||||||
|
|
||||||
|
count++
|
||||||
|
finish.push(animation.finished)
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(finish).then(() => {
|
||||||
|
this.$emit('done', this.id)
|
||||||
|
this.$accessor.chat.delEmote(this.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="emoji">
|
<div class="emotes">
|
||||||
<ul>
|
<ul v-if="!muted">
|
||||||
<li><i @click.stop.prevent="click('heart')" class="fas fa-heart"></i></li>
|
<li><i @click.stop.prevent="click('heart')" class="fas fa-heart"></i></li>
|
||||||
<li><i @click.stop.prevent="click('poo')" class="fas fa-poo"></i></li>
|
<li><i @click.stop.prevent="click('poo')" class="fas fa-poo"></i></li>
|
||||||
<li><i @click.stop.prevent="click('grin')" class="fas fa-grin-tears"></i></li>
|
<li><i @click.stop.prevent="click('grin')" class="fas fa-grin-tears"></i></li>
|
||||||
<li><i @click.stop.prevent="click('dizzy')" class="fas fa-dizzy"></i></li>
|
<li><i @click.stop.prevent="click('ghost')" class="fas fa-ghost"></i></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.emoji {
|
.emotes {
|
||||||
ul {
|
ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -33,16 +33,15 @@
|
|||||||
import { Vue, Component } from 'vue-property-decorator'
|
import { Vue, Component } from 'vue-property-decorator'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'neko-emoji',
|
name: 'neko-emotes',
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
click(emoji: string) {
|
get muted() {
|
||||||
this.$swal({
|
return this.$accessor.user.muted
|
||||||
title: 'Error!',
|
}
|
||||||
text: 'This feature is not available yet',
|
|
||||||
icon: 'error',
|
click(emote: string) {
|
||||||
confirmButtonText: 'Cool',
|
this.$accessor.chat.sendEmote(emote)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
116
client/src/components/header.vue
Normal file
116
client/src/components/header.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<div class="neko">
|
||||||
|
<img src="@/assets/logo.svg" alt="n.eko" />
|
||||||
|
<span><b>n</b>.eko</span>
|
||||||
|
</div>
|
||||||
|
<ul class="menu">
|
||||||
|
<li>
|
||||||
|
<i
|
||||||
|
:class="[{ disabled: !admin }, { 'fa-lock-open': !locked }, { 'fa-lock': locked }, 'fas', 'lock']"
|
||||||
|
@click="toggleLock"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-bars toggle" @click="toggleMenu" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.header {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.neko {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
width: 150px;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
height: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
justify-self: flex-end;
|
||||||
|
margin-right: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: block;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 32px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-lock {
|
||||||
|
color: rgba($color: $style-error, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
background: $background-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component({ name: 'neko-settings' })
|
||||||
|
export default class extends Vue {
|
||||||
|
get admin() {
|
||||||
|
return this.$accessor.user.admin
|
||||||
|
}
|
||||||
|
|
||||||
|
get locked() {
|
||||||
|
return this.$accessor.locked
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMenu() {
|
||||||
|
this.$accessor.client.toggleSide()
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLock() {
|
||||||
|
if (this.admin) {
|
||||||
|
if (this.locked) {
|
||||||
|
this.$accessor.remote.unlock()
|
||||||
|
} else {
|
||||||
|
this.$accessor.remote.lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -11,45 +11,19 @@
|
|||||||
<li
|
<li
|
||||||
v-if="member.id !== id && member.connected"
|
v-if="member.id !== id && member.connected"
|
||||||
:key="index"
|
:key="index"
|
||||||
v-tooltip="{ content: member.username, placement: 'top', offset: 5, boundariesElement: 'body' }"
|
v-tooltip="{ content: member.username, placement: 'bottom', offset: -15, boundariesElement: 'body' }"
|
||||||
>
|
>
|
||||||
<div :class="[{ host: member.id === host, admin: member.admin }, 'member']">
|
<div :class="[{ host: member.id === host, admin: member.admin }, 'member']">
|
||||||
<img
|
<img
|
||||||
:src="`https://api.adorable.io/avatars/50/${member.username}.png`"
|
:src="`https://api.adorable.io/avatars/50/${member.username}.png`"
|
||||||
@contextmenu="context($event, { member, index })"
|
@contextmenu.stop.prevent="onContext($event, { member })"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<vue-context class="context" ref="menu">
|
<neko-context ref="context" />
|
||||||
<template slot-scope="child" v-if="child.data && admin">
|
|
||||||
<li>
|
|
||||||
<strong>{{ child.data.member.username }}</strong>
|
|
||||||
</li>
|
|
||||||
<li class="seperator" />
|
|
||||||
<li>
|
|
||||||
<span @click="mute(child.data.member)" v-if="!child.data.member.muted">Mute</span>
|
|
||||||
<span @click="unmute(child.data.member)" v-else>Unmute</span>
|
|
||||||
</li>
|
|
||||||
<template v-if="child.data.member.id === host">
|
|
||||||
<li>
|
|
||||||
<span @click="release(child.data.member)">Release Controls</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span @click="control(child.data.member)">Take Controls</span>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<li class="seperator" />
|
|
||||||
<li>
|
|
||||||
<span @click="kick(child.data.member)" style="color: #f04747">Kick</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span @click="ban(child.data.member)" style="color: #f04747">Ban</span>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</vue-context>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -179,95 +153,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.context {
|
|
||||||
background-color: $background-floating;
|
|
||||||
background-clip: padding-box;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 5px;
|
|
||||||
min-width: 150px;
|
|
||||||
z-index: 1500;
|
|
||||||
position: fixed;
|
|
||||||
list-style: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-height: calc(100% - 50px);
|
|
||||||
overflow-y: auto;
|
|
||||||
color: $interactive-normal;
|
|
||||||
user-select: none;
|
|
||||||
box-shadow: $elevation-high;
|
|
||||||
|
|
||||||
> li {
|
|
||||||
margin: 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.seperator {
|
|
||||||
height: 1px;
|
|
||||||
background: $background-secondary;
|
|
||||||
margin: 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> strong {
|
|
||||||
display: block;
|
|
||||||
padding: 8px 5px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
padding: 5px;
|
|
||||||
font-weight: 400;
|
|
||||||
text-decoration: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
background-color: transparent;
|
|
||||||
border-radius: 3px;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: $background-modifier-hover;
|
|
||||||
color: $interactive-hover;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||||
import { Member } from '~/client/types'
|
import { Member } from '~/neko/types'
|
||||||
|
|
||||||
// @ts-ignore
|
import Content from './context.vue'
|
||||||
import { VueContext } from 'vue-context'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'neko-members',
|
name: 'neko-members',
|
||||||
components: {
|
components: {
|
||||||
'vue-context': VueContext,
|
'neko-context': Content,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
@Ref('menu') readonly menu!: any
|
@Ref('context') readonly _context!: any
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this.$accessor.user.id
|
return this.$accessor.user.id
|
||||||
}
|
}
|
||||||
|
|
||||||
get admin() {
|
|
||||||
return this.$accessor.user.admin
|
|
||||||
}
|
|
||||||
|
|
||||||
get host() {
|
get host() {
|
||||||
return this.$accessor.remote.id
|
return this.$accessor.remote.id
|
||||||
}
|
}
|
||||||
@ -280,75 +187,8 @@
|
|||||||
return this.$accessor.user.members
|
return this.$accessor.user.members
|
||||||
}
|
}
|
||||||
|
|
||||||
context(event: MouseEvent, data: any) {
|
onContext(event: MouseEvent, data: any) {
|
||||||
if (this.admin) {
|
this._context.open(event, data)
|
||||||
event.preventDefault()
|
|
||||||
this.menu.open(event, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kick(member: Member) {
|
|
||||||
this.$swal({
|
|
||||||
title: `Kick ${member.username}?`,
|
|
||||||
text: `Are you sure you want to kick ${member.username}?`,
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Yes',
|
|
||||||
}).then(({ value }) => {
|
|
||||||
if (value) {
|
|
||||||
this.$accessor.user.kick(member)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ban(member: Member) {
|
|
||||||
this.$swal({
|
|
||||||
title: `Ban ${member.username}?`,
|
|
||||||
text: `Are you sure you want to ban ${member.username}? You will need to restart the server to undo this.`,
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Yes',
|
|
||||||
}).then(({ value }) => {
|
|
||||||
if (value) {
|
|
||||||
this.$accessor.user.ban(member)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
mute(member: Member) {
|
|
||||||
this.$swal({
|
|
||||||
title: `Mute ${member.username}?`,
|
|
||||||
text: `Are you sure you want to mute ${member.username}?`,
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Yes',
|
|
||||||
}).then(({ value }) => {
|
|
||||||
if (value) {
|
|
||||||
this.$accessor.user.mute(member)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
unmute(member: Member) {
|
|
||||||
this.$swal({
|
|
||||||
title: `Unmute ${member.username}?`,
|
|
||||||
text: `Are you sure you want to unmute ${member.username}?`,
|
|
||||||
icon: 'warning',
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonText: 'Yes',
|
|
||||||
}).then(({ value }) => {
|
|
||||||
if (value) {
|
|
||||||
this.$accessor.user.unmute(member)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
release(member: Member) {
|
|
||||||
this.$accessor.remote.adminRelease()
|
|
||||||
}
|
|
||||||
|
|
||||||
control(member: Member) {
|
|
||||||
this.$accessor.remote.adminControl()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,12 +1,216 @@
|
|||||||
<template>
|
<template>
|
||||||
<div></div>
|
<div class="settings">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<span>Scroll Sensitivity</span>
|
||||||
|
<label class="slider">
|
||||||
|
<input type="range" min="5" max="100" v-model="scroll" />
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Invert Scroll</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" v-model="scroll_invert" />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Autoplay Video</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" v-model="autoplay" />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Ignore Emotes</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" v-model="ignore_emotes" />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span>Play Chat Sound</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" v-model="chat_sound" />
|
||||||
|
<span />
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.settings {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 5px 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom: 1px solid $background-secondary;
|
||||||
|
padding: 5px 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: auto;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
justify-self: flex-end;
|
||||||
|
position: relative;
|
||||||
|
width: 42px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: $background-tertiary;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 34px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
transition: 0.3s;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
&:checked + span {
|
||||||
|
background-color: $style-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + span:before {
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 120px;
|
||||||
|
|
||||||
|
input[type='range'] {
|
||||||
|
display: inline-block;
|
||||||
|
background: transparent;
|
||||||
|
appearance: none;
|
||||||
|
height: 24px;
|
||||||
|
max-width: 120px;
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: $interactive-active;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: $style-primary;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
height: 12px;
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: $interactive-active;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: $style-primary;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
@Component({ name: 'neko-settings' })
|
@Component({ name: 'neko-settings' })
|
||||||
export default class extends Vue {}
|
export default class extends Vue {
|
||||||
|
get scroll() {
|
||||||
|
return this.$accessor.settings.scroll.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
set scroll(value: string) {
|
||||||
|
this.$accessor.settings.setScroll(parseInt(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
get scroll_invert() {
|
||||||
|
return this.$accessor.settings.scroll_invert
|
||||||
|
}
|
||||||
|
|
||||||
|
set scroll_invert(value: boolean) {
|
||||||
|
this.$accessor.settings.setInvert(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get autoplay() {
|
||||||
|
return this.$accessor.settings.autoplay
|
||||||
|
}
|
||||||
|
|
||||||
|
set autoplay(value: boolean) {
|
||||||
|
this.$accessor.settings.setAutoplay(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get ignore_emotes() {
|
||||||
|
return this.$accessor.settings.ignore_emotes
|
||||||
|
}
|
||||||
|
|
||||||
|
set ignore_emotes(value: boolean) {
|
||||||
|
this.$accessor.settings.setIgnore(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get chat_sound() {
|
||||||
|
return this.$accessor.settings.chat_sound
|
||||||
|
}
|
||||||
|
|
||||||
|
set chat_sound(value: boolean) {
|
||||||
|
this.$accessor.settings.setSound(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
<div ref="player" class="player">
|
<div ref="player" class="player">
|
||||||
<div ref="container" class="player-container">
|
<div ref="container" class="player-container">
|
||||||
<video ref="video" />
|
<video ref="video" />
|
||||||
|
<div class="emotes">
|
||||||
|
<template v-for="(emote, index) in emotes">
|
||||||
|
<neko-emote :id="index" :key="index" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="overlay"
|
ref="overlay"
|
||||||
class="overlay"
|
class="overlay"
|
||||||
@ -73,12 +78,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-overlay {
|
.player-overlay,
|
||||||
|
.emotes {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-overlay {
|
||||||
background: rgba($color: #000, $alpha: 0.2);
|
background: rgba($color: #000, $alpha: 0.2);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -118,7 +128,14 @@
|
|||||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||||
import ResizeObserver from 'resize-observer-polyfill'
|
import ResizeObserver from 'resize-observer-polyfill'
|
||||||
|
|
||||||
@Component({ name: 'neko-video' })
|
import Emote from './emote.vue'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'neko-video',
|
||||||
|
components: {
|
||||||
|
'neko-emote': Emote,
|
||||||
|
},
|
||||||
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
@Ref('component') readonly _component!: HTMLElement
|
@Ref('component') readonly _component!: HTMLElement
|
||||||
@Ref('container') readonly _container!: HTMLElement
|
@Ref('container') readonly _container!: HTMLElement
|
||||||
@ -163,6 +180,22 @@
|
|||||||
return this.$accessor.video.playable
|
return this.$accessor.video.playable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get emotes() {
|
||||||
|
return this.$accessor.chat.emotes
|
||||||
|
}
|
||||||
|
|
||||||
|
get autoplay() {
|
||||||
|
return this.$accessor.settings.autoplay
|
||||||
|
}
|
||||||
|
|
||||||
|
get scroll() {
|
||||||
|
return this.$accessor.settings.scroll
|
||||||
|
}
|
||||||
|
|
||||||
|
get scroll_invert() {
|
||||||
|
return this.$accessor.settings.scroll_invert
|
||||||
|
}
|
||||||
|
|
||||||
@Watch('volume')
|
@Watch('volume')
|
||||||
onVolumeChanged(volume: number) {
|
onVolumeChanged(volume: number) {
|
||||||
if (this._video) {
|
if (this._video) {
|
||||||
@ -189,11 +222,6 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this._video.src = window.URL.createObjectURL(this.stream) // for older browsers
|
this._video.src = window.URL.createObjectURL(this.stream) // for older browsers
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._video.paused && this.playing) {
|
|
||||||
// TODO: auto play setting
|
|
||||||
this.play()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch('playing')
|
@Watch('playing')
|
||||||
@ -220,6 +248,9 @@
|
|||||||
|
|
||||||
this._video.addEventListener('canplaythrough', () => {
|
this._video.addEventListener('canplaythrough', () => {
|
||||||
this.$accessor.video.setPlayable(true)
|
this.$accessor.video.setPlayable(true)
|
||||||
|
if (this.autoplay) {
|
||||||
|
this.$accessor.video.play()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this._video.addEventListener('ended', () => {
|
this._video.addEventListener('ended', () => {
|
||||||
@ -291,10 +322,19 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.onMousePos(e)
|
this.onMousePos(e)
|
||||||
this.$client.sendData('wheel', {
|
|
||||||
x: (e.deltaX * -1) / 10,
|
let x = e.deltaX
|
||||||
y: (e.deltaY * -1) / 10,
|
let y = e.deltaY
|
||||||
}) // TODO: Add user settings
|
|
||||||
|
if (this.scroll_invert) {
|
||||||
|
x = x * -1
|
||||||
|
y = y * -1
|
||||||
|
}
|
||||||
|
|
||||||
|
x = Math.min(Math.max(x, -this.scroll), this.scroll)
|
||||||
|
y = Math.min(Math.max(y, -this.scroll), this.scroll)
|
||||||
|
|
||||||
|
this.$client.sendData('wheel', { x, y })
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(e: MouseEvent) {
|
onMouseDown(e: MouseEvent) {
|
||||||
|
@ -7,6 +7,7 @@ import ToolTip from 'v-tooltip'
|
|||||||
import Client from './plugins/neko'
|
import Client from './plugins/neko'
|
||||||
import Axios from './plugins/axios'
|
import Axios from './plugins/axios'
|
||||||
import Swal from './plugins/swal'
|
import Swal from './plugins/swal'
|
||||||
|
import Anime from './plugins/anime'
|
||||||
|
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import app from './app.vue'
|
import app from './app.vue'
|
||||||
@ -17,6 +18,7 @@ Vue.use(Notifications)
|
|||||||
Vue.use(ToolTip)
|
Vue.use(ToolTip)
|
||||||
Vue.use(Axios)
|
Vue.use(Axios)
|
||||||
Vue.use(Swal)
|
Vue.use(Swal)
|
||||||
|
Vue.use(Anime)
|
||||||
Vue.use(Client)
|
Vue.use(Client)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
|
@ -50,15 +50,20 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
if (username === '') {
|
if (username === '') {
|
||||||
throw new Error('Must add a username') // TODO: Better handleing
|
throw new Error('Must add a username') // TODO: Better handleing
|
||||||
}
|
}
|
||||||
this._username = username
|
|
||||||
|
|
||||||
this._ws = new WebSocket(`${url}ws?password=${password}`)
|
this._username = username
|
||||||
this.emit('debug', `connecting to ${this._ws.url}`)
|
|
||||||
this._ws.onmessage = this.onMessage.bind(this)
|
|
||||||
this._ws.onerror = event => this.onError.bind(this)
|
|
||||||
this._ws.onclose = event => this.onDisconnected.bind(this, new Error('websocket closed'))
|
|
||||||
this._timeout = setTimeout(this.onTimeout.bind(this), 5000)
|
|
||||||
this[EVENT.CONNECTING]()
|
this[EVENT.CONNECTING]()
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._ws = new WebSocket(`${url}ws?password=${password}`)
|
||||||
|
this.emit('debug', `connecting to ${this._ws.url}`)
|
||||||
|
this._ws.onmessage = this.onMessage.bind(this)
|
||||||
|
this._ws.onerror = event => this.onError.bind(this)
|
||||||
|
this._ws.onclose = event => this.onDisconnected.bind(this, new Error('websocket closed'))
|
||||||
|
this._timeout = setTimeout(this.onTimeout.bind(this), 5000)
|
||||||
|
} catch (err) {
|
||||||
|
this.onDisconnected(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendData(event: 'wheel' | 'mousemove', data: { x: number; y: number }): void
|
public sendData(event: 'wheel' | 'mousemove', data: { x: number; y: number }): void
|
||||||
@ -163,6 +168,9 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
case 'connected':
|
case 'connected':
|
||||||
this.onConnected()
|
this.onConnected()
|
||||||
break
|
break
|
||||||
|
case 'failed':
|
||||||
|
this.onDisconnected(new Error('peer failed'))
|
||||||
|
break
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
this.onDisconnected(new Error('peer disconnected'))
|
this.onDisconnected(new Error('peer disconnected'))
|
||||||
break
|
break
|
@ -29,10 +29,11 @@ export const EVENT = {
|
|||||||
RELEASE: 'control/release',
|
RELEASE: 'control/release',
|
||||||
REQUEST: 'control/request',
|
REQUEST: 'control/request',
|
||||||
REQUESTING: 'control/requesting',
|
REQUESTING: 'control/requesting',
|
||||||
|
GIVE: 'control/give',
|
||||||
},
|
},
|
||||||
CHAT: {
|
CHAT: {
|
||||||
MESSAGE: 'chat/message',
|
MESSAGE: 'chat/message',
|
||||||
EMOJI: 'chat/emoji',
|
EMOTE: 'chat/emote',
|
||||||
},
|
},
|
||||||
ADMIN: {
|
ADMIN: {
|
||||||
BAN: 'admin/ban',
|
BAN: 'admin/ban',
|
||||||
@ -43,6 +44,7 @@ export const EVENT = {
|
|||||||
UNMUTE: 'admin/unmute',
|
UNMUTE: 'admin/unmute',
|
||||||
CONTROL: 'admin/control',
|
CONTROL: 'admin/control',
|
||||||
RELEASE: 'admin/release',
|
RELEASE: 'admin/release',
|
||||||
|
GIVE: 'admin/give',
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
@ -57,12 +59,17 @@ export type WebSocketEvents =
|
|||||||
| ChatEvents
|
| ChatEvents
|
||||||
| AdminEvents
|
| AdminEvents
|
||||||
|
|
||||||
|
export type ControlEvents =
|
||||||
|
| typeof EVENT.CONTROL.LOCKED
|
||||||
|
| typeof EVENT.CONTROL.RELEASE
|
||||||
|
| typeof EVENT.CONTROL.REQUEST
|
||||||
|
| typeof EVENT.CONTROL.GIVE
|
||||||
|
|
||||||
export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT
|
export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT
|
||||||
export type ControlEvents = typeof EVENT.CONTROL.LOCKED | typeof EVENT.CONTROL.RELEASE | typeof EVENT.CONTROL.REQUEST
|
|
||||||
export type IdentityEvents = typeof EVENT.IDENTITY.PROVIDE | typeof EVENT.IDENTITY.DETAILS
|
export type IdentityEvents = typeof EVENT.IDENTITY.PROVIDE | typeof EVENT.IDENTITY.DETAILS
|
||||||
export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED
|
export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED
|
||||||
export type SignalEvents = typeof EVENT.SIGNAL.ANSWER | typeof EVENT.SIGNAL.PROVIDE
|
export type SignalEvents = typeof EVENT.SIGNAL.ANSWER | typeof EVENT.SIGNAL.PROVIDE
|
||||||
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOJI
|
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
|
||||||
export type AdminEvents =
|
export type AdminEvents =
|
||||||
| typeof EVENT.ADMIN.BAN
|
| typeof EVENT.ADMIN.BAN
|
||||||
| typeof EVENT.ADMIN.KICK
|
| typeof EVENT.ADMIN.KICK
|
||||||
@ -72,3 +79,4 @@ export type AdminEvents =
|
|||||||
| typeof EVENT.ADMIN.UNMUTE
|
| typeof EVENT.ADMIN.UNMUTE
|
||||||
| typeof EVENT.ADMIN.CONTROL
|
| typeof EVENT.ADMIN.CONTROL
|
||||||
| typeof EVENT.ADMIN.RELEASE
|
| typeof EVENT.ADMIN.RELEASE
|
||||||
|
| typeof EVENT.ADMIN.GIVE
|
@ -12,8 +12,9 @@ import {
|
|||||||
MemberDisconnectPayload,
|
MemberDisconnectPayload,
|
||||||
MemberPayload,
|
MemberPayload,
|
||||||
ControlPayload,
|
ControlPayload,
|
||||||
|
ControlTargetPayload,
|
||||||
ChatPayload,
|
ChatPayload,
|
||||||
EmojiPayload,
|
EmotePayload,
|
||||||
AdminPayload,
|
AdminPayload,
|
||||||
AdminTargetPayload,
|
AdminTargetPayload,
|
||||||
} from './messages'
|
} from './messages'
|
||||||
@ -60,20 +61,27 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
speed: 1000,
|
speed: 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
|
id: this.id,
|
||||||
|
content: 'connected',
|
||||||
|
type: 'event',
|
||||||
|
created: new Date(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.DISCONNECTED](reason?: Error) {
|
protected [EVENT.DISCONNECTED](reason?: Error) {
|
||||||
this.$accessor.setConnected(false)
|
this.$accessor.setConnected(false)
|
||||||
|
|
||||||
this.$accessor.remote.clearHost()
|
this.$accessor.remote.clear()
|
||||||
this.$accessor.user.clearMembers()
|
this.$accessor.user.clear()
|
||||||
this.$accessor.video.clear()
|
this.$accessor.video.clear()
|
||||||
this.$accessor.chat.clear()
|
this.$accessor.chat.clear()
|
||||||
|
|
||||||
this.$vue.$notify({
|
this.$vue.$notify({
|
||||||
group: 'neko',
|
group: 'neko',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: `Disconnected`,
|
title: `Disconnected:`,
|
||||||
text: reason ? reason.message : undefined,
|
text: reason ? reason.message : undefined,
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
speed: 1000,
|
speed: 1000,
|
||||||
@ -98,7 +106,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
protected [EVENT.SYSTEM.DISCONNECT]({ message }: DisconnectPayload) {
|
protected [EVENT.SYSTEM.DISCONNECT]({ message }: DisconnectPayload) {
|
||||||
this.onDisconnected(new Error(message))
|
this.onDisconnected(new Error(message))
|
||||||
this.$vue.$swal({
|
this.$vue.$swal({
|
||||||
title: 'Error!',
|
title: 'Disconnected!',
|
||||||
text: message,
|
text: message,
|
||||||
icon: 'error',
|
icon: 'error',
|
||||||
confirmButtonText: 'ok',
|
confirmButtonText: 'ok',
|
||||||
@ -123,7 +131,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
this.$accessor.user.addMember(member)
|
this.$accessor.user.addMember(member)
|
||||||
|
|
||||||
if (member.id !== this.id) {
|
if (member.id !== this.id) {
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id: member.id,
|
id: member.id,
|
||||||
content: 'connected',
|
content: 'connected',
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -138,7 +146,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id: member.id,
|
id: member.id,
|
||||||
content: 'disconnected',
|
content: 'disconnected',
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -166,18 +174,18 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
speed: 1000,
|
speed: 1000,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
this.$accessor.chat.addMessage({
|
|
||||||
id: member.id,
|
|
||||||
content: 'took the controls',
|
|
||||||
type: 'event',
|
|
||||||
created: new Date(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
|
id: member.id,
|
||||||
|
content: 'took the controls',
|
||||||
|
type: 'event',
|
||||||
|
created: new Date(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.CONTROL.RELEASE]({ id }: ControlPayload) {
|
protected [EVENT.CONTROL.RELEASE]({ id }: ControlPayload) {
|
||||||
this.$accessor.remote.clearHost()
|
this.$accessor.remote.clear()
|
||||||
const member = this.member(id)
|
const member = this.member(id)
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return
|
return
|
||||||
@ -191,14 +199,14 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
duration: 5000,
|
duration: 5000,
|
||||||
speed: 1000,
|
speed: 1000,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
this.$accessor.chat.addMessage({
|
|
||||||
id: member.id,
|
|
||||||
content: 'released the controls',
|
|
||||||
type: 'event',
|
|
||||||
created: new Date(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
|
id: member.id,
|
||||||
|
content: 'released the controls',
|
||||||
|
type: 'event',
|
||||||
|
created: new Date(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.CONTROL.REQUEST]({ id }: ControlPayload) {
|
protected [EVENT.CONTROL.REQUEST]({ id }: ControlPayload) {
|
||||||
@ -219,7 +227,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
|
|
||||||
protected [EVENT.CONTROL.REQUESTING]({ id }: ControlPayload) {
|
protected [EVENT.CONTROL.REQUESTING]({ id }: ControlPayload) {
|
||||||
const member = this.member(id)
|
const member = this.member(id)
|
||||||
if (!member) {
|
if (!member || member.ignored) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,11 +240,31 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected [EVENT.CONTROL.GIVE]({ id, target }: ControlTargetPayload) {
|
||||||
|
const member = this.member(target)
|
||||||
|
if (!member) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$accessor.remote.setHost(member)
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
|
id,
|
||||||
|
content: `gave the controls to ${member.id == this.id ? 'you' : member.username}`,
|
||||||
|
type: 'event',
|
||||||
|
created: new Date(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Chat Events
|
// Chat Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
protected [EVENT.CHAT.MESSAGE]({ id, content }: ChatPayload) {
|
protected [EVENT.CHAT.MESSAGE]({ id, content }: ChatPayload) {
|
||||||
this.$accessor.chat.addMessage({
|
const member = this.member(id)
|
||||||
|
if (!member || member.ignored) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content,
|
content,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@ -244,8 +272,13 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.CHAT.EMOJI]({ id, emoji }: EmojiPayload) {
|
protected [EVENT.CHAT.EMOTE]({ id, emote }: EmotePayload) {
|
||||||
//
|
const member = this.member(id)
|
||||||
|
if (!member || member.ignored) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$accessor.chat.newEmote({ type: emote })
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
@ -261,9 +294,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `banned ${member.username}`,
|
content: `banned ${member.id == this.id ? 'you' : member.username}`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
})
|
})
|
||||||
@ -279,9 +312,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `kicked ${member.username}`,
|
content: `kicked ${member.id == this.id ? 'you' : member.username}`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
})
|
})
|
||||||
@ -299,9 +332,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `muted ${member.username}`,
|
content: `muted ${member.id == this.id ? 'you' : member.username}`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
})
|
})
|
||||||
@ -319,7 +352,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `unmuted ${member.username}`,
|
content: `unmuted ${member.username}`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -328,7 +361,8 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.ADMIN.LOCK]({ id }: AdminPayload) {
|
protected [EVENT.ADMIN.LOCK]({ id }: AdminPayload) {
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.setLocked(true)
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `locked the room`,
|
content: `locked the room`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -337,7 +371,8 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.ADMIN.UNLOCK]({ id }: AdminPayload) {
|
protected [EVENT.ADMIN.UNLOCK]({ id }: AdminPayload) {
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.setLocked(false)
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `unlocked the room`,
|
content: `unlocked the room`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -349,7 +384,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
this.$accessor.remote.setHost(id)
|
this.$accessor.remote.setHost(id)
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `force took the controls`,
|
content: `force took the controls`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -363,18 +398,18 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `took the controls from ${member.username}`,
|
content: `took the controls from ${member.id == this.id ? 'you' : member.username}`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.ADMIN.RELEASE]({ id, target }: AdminTargetPayload) {
|
protected [EVENT.ADMIN.RELEASE]({ id, target }: AdminTargetPayload) {
|
||||||
this.$accessor.remote.clearHost()
|
this.$accessor.remote.clear()
|
||||||
if (!target) {
|
if (!target) {
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `force released the controls`,
|
content: `force released the controls`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
@ -388,9 +423,29 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$accessor.chat.addMessage({
|
this.$accessor.chat.newMessage({
|
||||||
id,
|
id,
|
||||||
content: `released the controls from ${member.username}`,
|
content: `released the controls from ${member.id == this.id ? 'you' : member.username}`,
|
||||||
|
type: 'event',
|
||||||
|
created: new Date(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected [EVENT.ADMIN.GIVE]({ id, target }: AdminTargetPayload) {
|
||||||
|
if (!target) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = this.member(target)
|
||||||
|
if (!member) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$accessor.remote.setHost(member)
|
||||||
|
|
||||||
|
this.$accessor.chat.newMessage({
|
||||||
|
id,
|
||||||
|
content: `gave the controls to ${member.id == this.id ? 'you' : member.username}`,
|
||||||
type: 'event',
|
type: 'event',
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
})
|
})
|
@ -1,4 +1,14 @@
|
|||||||
import { WebSocketEvents, EVENT } from './events'
|
import {
|
||||||
|
EVENT,
|
||||||
|
WebSocketEvents,
|
||||||
|
SystemEvents,
|
||||||
|
ControlEvents,
|
||||||
|
IdentityEvents,
|
||||||
|
MemberEvents,
|
||||||
|
SignalEvents,
|
||||||
|
ChatEvents,
|
||||||
|
AdminEvents,
|
||||||
|
} from './events'
|
||||||
import { Member } from './types'
|
import { Member } from './types'
|
||||||
|
|
||||||
export type WebSocketMessages =
|
export type WebSocketMessages =
|
||||||
@ -89,12 +99,17 @@ export interface MemberDisconnectPayload {
|
|||||||
*/
|
*/
|
||||||
// control/locked & control/release & control/request
|
// control/locked & control/release & control/request
|
||||||
export interface ControlMessage extends WebSocketMessage, ControlPayload {
|
export interface ControlMessage extends WebSocketMessage, ControlPayload {
|
||||||
event: typeof EVENT.CONTROL.LOCKED | typeof EVENT.CONTROL.RELEASE | typeof EVENT.CONTROL.REQUEST
|
event: ControlEvents
|
||||||
}
|
}
|
||||||
export interface ControlPayload {
|
export interface ControlPayload {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ControlTargetPayload {
|
||||||
|
id: string
|
||||||
|
target: string
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CHAT PAYLOADS
|
CHAT PAYLOADS
|
||||||
*/
|
*/
|
||||||
@ -112,24 +127,24 @@ export interface ChatPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// chat/emoji
|
// chat/emoji
|
||||||
export interface ChatEmojiMessage extends WebSocketMessage, EmojiPayload {
|
export interface ChatEmoteMessage extends WebSocketMessage, EmotePayload {
|
||||||
event: typeof EVENT.CHAT.EMOJI
|
event: typeof EVENT.CHAT.EMOTE
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiPayload {
|
export interface EmotePayload {
|
||||||
id: string
|
id: string
|
||||||
emoji: string
|
emote: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiSendPayload {
|
export interface EmojiSendPayload {
|
||||||
emoji: string
|
emote: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ADMIN PAYLOADS
|
ADMIN PAYLOADS
|
||||||
*/
|
*/
|
||||||
export interface AdminMessage extends WebSocketMessage, AdminPayload {
|
export interface AdminMessage extends WebSocketMessage, AdminPayload {
|
||||||
event: typeof EVENT.MESSAGE
|
event: AdminEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminPayload {
|
export interface AdminPayload {
|
||||||
@ -137,7 +152,7 @@ export interface AdminPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminTargetMessage extends WebSocketMessage, AdminTargetPayload {
|
export interface AdminTargetMessage extends WebSocketMessage, AdminTargetPayload {
|
||||||
event: typeof EVENT.CHAT.EMOJI
|
event: AdminEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminTargetPayload {
|
export interface AdminTargetPayload {
|
@ -4,4 +4,5 @@ export interface Member {
|
|||||||
admin: boolean
|
admin: boolean
|
||||||
muted: boolean
|
muted: boolean
|
||||||
connected?: boolean
|
connected?: boolean
|
||||||
|
ignored?: boolean
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { PluginObject } from 'vue'
|
import { PluginObject } from 'vue'
|
||||||
import { NekoClient } from '~/client'
|
import { NekoClient } from '~/neko'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
const $client: NekoClient
|
const $client: NekoClient
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import VueRouter from 'vue-router'
|
|
||||||
import chat from '~/pages/chat.vue'
|
|
||||||
import about from '~/pages/about.vue'
|
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'chat',
|
|
||||||
component: chat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
name: 'about',
|
|
||||||
component: about,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = new VueRouter({
|
|
||||||
mode: 'history',
|
|
||||||
base: process.env.BASE_URL,
|
|
||||||
routes,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
@ -1,9 +1,18 @@
|
|||||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
||||||
import { EVENT } from '~/client/events'
|
import { makeid } from '~/utils'
|
||||||
|
import { EVENT } from '~/neko/events'
|
||||||
import { accessor } from '~/store'
|
import { accessor } from '~/store'
|
||||||
|
|
||||||
export const namespaced = true
|
export const namespaced = true
|
||||||
|
|
||||||
|
interface Emote {
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emotes {
|
||||||
|
[id: string]: Emote
|
||||||
|
}
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
id: string
|
id: string
|
||||||
content: string
|
content: string
|
||||||
@ -13,6 +22,7 @@ interface Message {
|
|||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
history: [] as Message[],
|
history: [] as Message[],
|
||||||
|
emotes: {} as Emotes,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = getterTree(state, {
|
export const getters = getterTree(state, {
|
||||||
@ -23,7 +33,24 @@ export const mutations = mutationTree(state, {
|
|||||||
addMessage(state, message: Message) {
|
addMessage(state, message: Message) {
|
||||||
state.history = state.history.concat([message])
|
state.history = state.history.concat([message])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addEmote(state, { id, emote }: { id: string; emote: Emote }) {
|
||||||
|
state.emotes = {
|
||||||
|
...state.emotes,
|
||||||
|
[id]: emote,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
delEmote(state, id: string) {
|
||||||
|
const emotes = {
|
||||||
|
...state.emotes,
|
||||||
|
}
|
||||||
|
delete emotes[id]
|
||||||
|
state.emotes = emotes
|
||||||
|
},
|
||||||
|
|
||||||
clear(state) {
|
clear(state) {
|
||||||
|
state.emotes = {}
|
||||||
state.history = []
|
state.history = []
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -31,20 +58,34 @@ export const mutations = mutationTree(state, {
|
|||||||
export const actions = actionTree(
|
export const actions = actionTree(
|
||||||
{ state, getters, mutations },
|
{ state, getters, mutations },
|
||||||
{
|
{
|
||||||
|
newEmote(store, emote: Emote) {
|
||||||
|
if (accessor.settings.ignore_emotes) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = makeid(10)
|
||||||
|
accessor.chat.addEmote({ id, emote })
|
||||||
|
},
|
||||||
|
|
||||||
|
newMessage({ state }, message: Message) {
|
||||||
|
if (accessor.settings.chat_sound) {
|
||||||
|
new Audio('chat.mp3').play().catch(console.error)
|
||||||
|
}
|
||||||
|
accessor.chat.addMessage(message)
|
||||||
|
},
|
||||||
|
|
||||||
sendMessage(store, content: string) {
|
sendMessage(store, content: string) {
|
||||||
if (!accessor.connected || accessor.user.muted) {
|
if (!accessor.connected || accessor.user.muted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$client.sendMessage(EVENT.CHAT.MESSAGE, { content })
|
$client.sendMessage(EVENT.CHAT.MESSAGE, { content })
|
||||||
},
|
},
|
||||||
|
|
||||||
sendEmoji(store, emoji: string) {
|
sendEmote(store, emote: string) {
|
||||||
if (!accessor.connected || !accessor.user.muted) {
|
if (!accessor.connected || accessor.user.muted) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
$client.sendMessage(EVENT.CHAT.EMOTE, { emote })
|
||||||
$client.sendMessage(EVENT.CHAT.EMOJI, { emoji })
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,35 +1,22 @@
|
|||||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
||||||
|
import { get, set } from '~/utils/localstorage'
|
||||||
import { accessor } from '~/store'
|
import { accessor } from '~/store'
|
||||||
|
|
||||||
export const namespaced = true
|
export const namespaced = true
|
||||||
|
|
||||||
export const state = () => {
|
export const state = () => ({
|
||||||
let side = false
|
side: get<boolean>('side', false),
|
||||||
let _side = localStorage.getItem('side')
|
tab: get<string>('tab', 'chat'),
|
||||||
if (_side) {
|
about: false,
|
||||||
side = _side === '1'
|
about_page: '',
|
||||||
}
|
})
|
||||||
|
|
||||||
let tab = 'chat'
|
|
||||||
let _tab = localStorage.getItem('tab')
|
|
||||||
if (_tab) {
|
|
||||||
tab = _tab
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
side,
|
|
||||||
about: false,
|
|
||||||
about_page: '',
|
|
||||||
tab,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getters = getterTree(state, {})
|
export const getters = getterTree(state, {})
|
||||||
|
|
||||||
export const mutations = mutationTree(state, {
|
export const mutations = mutationTree(state, {
|
||||||
setTab(state, tab: string) {
|
setTab(state, tab: string) {
|
||||||
state.tab = tab
|
state.tab = tab
|
||||||
localStorage.setItem('tab', tab)
|
set('tab', tab)
|
||||||
},
|
},
|
||||||
setAbout(state, page: string) {
|
setAbout(state, page: string) {
|
||||||
state.about_page = page
|
state.about_page = page
|
||||||
@ -39,7 +26,7 @@ export const mutations = mutationTree(state, {
|
|||||||
},
|
},
|
||||||
toggleSide(state) {
|
toggleSide(state) {
|
||||||
state.side = !state.side
|
state.side = !state.side
|
||||||
localStorage.setItem('side', state.side ? '1' : '0')
|
set('side', state.side)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -12,17 +12,12 @@ import * as client from './client'
|
|||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
connecting: false,
|
connecting: false,
|
||||||
connected: false,
|
connected: false,
|
||||||
|
locked: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// type RootState = ReturnType<typeof state>
|
|
||||||
|
|
||||||
export const getters = {
|
|
||||||
// connected: (state: RootState) => state.connected
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mutations = mutationTree(state, {
|
export const mutations = mutationTree(state, {
|
||||||
initialiseStore(state) {
|
setLocked(state, locked: boolean) {
|
||||||
console.log('test')
|
state.locked = locked
|
||||||
},
|
},
|
||||||
|
|
||||||
setConnnecting(state) {
|
setConnnecting(state) {
|
||||||
@ -37,7 +32,7 @@ export const mutations = mutationTree(state, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const actions = actionTree(
|
export const actions = actionTree(
|
||||||
{ state, getters, mutations },
|
{ state, mutations },
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
connect(store, { username, password }: { username: string; password: string }) {
|
connect(store, { username, password }: { username: string; password: string }) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
||||||
import { Member } from '~/client/types'
|
import { Member } from '~/neko/types'
|
||||||
import { EVENT } from '~/client/events'
|
import { EVENT } from '~/neko/events'
|
||||||
import { accessor } from '~/store'
|
import { accessor } from '~/store'
|
||||||
|
|
||||||
export const namespaced = true
|
export const namespaced = true
|
||||||
@ -22,9 +22,6 @@ export const getters = getterTree(state, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const mutations = mutationTree(state, {
|
export const mutations = mutationTree(state, {
|
||||||
clearHost(state) {
|
|
||||||
state.id = ''
|
|
||||||
},
|
|
||||||
setHost(state, host: string | Member) {
|
setHost(state, host: string | Member) {
|
||||||
if (typeof host === 'string') {
|
if (typeof host === 'string') {
|
||||||
state.id = host
|
state.id = host
|
||||||
@ -32,6 +29,10 @@ export const mutations = mutationTree(state, {
|
|||||||
state.id = host.id
|
state.id = host.id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clear(state) {
|
||||||
|
state.id = ''
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const actions = actionTree(
|
export const actions = actionTree(
|
||||||
@ -68,6 +69,22 @@ export const actions = actionTree(
|
|||||||
$client.sendMessage(EVENT.CONTROL.RELEASE)
|
$client.sendMessage(EVENT.CONTROL.RELEASE)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
give({ getters }, member: string | Member) {
|
||||||
|
if (!accessor.connected || !getters.hosting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof member === 'string') {
|
||||||
|
member = accessor.user.members[member]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$client.sendMessage(EVENT.CONTROL.GIVE, { id: member.id })
|
||||||
|
},
|
||||||
|
|
||||||
adminControl() {
|
adminControl() {
|
||||||
if (!accessor.connected || !accessor.user.admin) {
|
if (!accessor.connected || !accessor.user.admin) {
|
||||||
return
|
return
|
||||||
@ -84,6 +101,22 @@ export const actions = actionTree(
|
|||||||
$client.sendMessage(EVENT.ADMIN.RELEASE)
|
$client.sendMessage(EVENT.ADMIN.RELEASE)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
adminGive({ getters }, member: string | Member) {
|
||||||
|
if (!accessor.connected) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof member === 'string') {
|
||||||
|
member = accessor.user.members[member]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$client.sendMessage(EVENT.ADMIN.GIVE, { id: member.id })
|
||||||
|
},
|
||||||
|
|
||||||
lock() {
|
lock() {
|
||||||
if (!accessor.connected || !accessor.user.admin) {
|
if (!accessor.connected || !accessor.user.admin) {
|
||||||
return
|
return
|
||||||
|
@ -1,30 +1,43 @@
|
|||||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
import { getterTree, mutationTree } from 'typed-vuex'
|
||||||
import { accessor } from '~/store'
|
import { get, set } from '~/utils/localstorage'
|
||||||
|
|
||||||
export const namespaced = true
|
export const namespaced = true
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => {
|
||||||
scroll: 10,
|
return {
|
||||||
scroll_invert: true,
|
scroll: get<number>('scroll', 10),
|
||||||
})
|
scroll_invert: get<boolean>('scroll_invert', true),
|
||||||
|
autoplay: get<boolean>('autoplay', true),
|
||||||
|
ignore_emotes: get<boolean>('ignore_emotes', false),
|
||||||
|
chat_sound: get<boolean>('chat_sound', true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getters = getterTree(state, {})
|
export const getters = getterTree(state, {})
|
||||||
|
|
||||||
export const mutations = mutationTree(state, {
|
export const mutations = mutationTree(state, {
|
||||||
setScroll(state, scroll: number) {
|
setScroll(state, scroll: number) {
|
||||||
state.scroll = scroll
|
state.scroll = scroll
|
||||||
localStorage.setItem('scroll', `${scroll}`)
|
set('scroll', scroll)
|
||||||
|
},
|
||||||
|
|
||||||
|
setInvert(state, value: boolean) {
|
||||||
|
state.scroll_invert = value
|
||||||
|
set('scroll_invert', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
setAutoplay(state, value: boolean) {
|
||||||
|
state.autoplay = value
|
||||||
|
set('autoplay', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
setIgnore(state, value: boolean) {
|
||||||
|
state.ignore_emotes = value
|
||||||
|
set('ignore_emotes', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
setSound(state, value: boolean) {
|
||||||
|
state.chat_sound = value
|
||||||
|
set('chat_sound', value)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const actions = actionTree(
|
|
||||||
{ state, getters, mutations },
|
|
||||||
{
|
|
||||||
initialise() {
|
|
||||||
const scroll = localStorage.getItem('scroll')
|
|
||||||
if (scroll) {
|
|
||||||
accessor.settings.setScroll(parseInt(scroll))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
||||||
import { Member } from '~/client/types'
|
import { Member } from '~/neko/types'
|
||||||
import { EVENT } from '~/client/events'
|
import { EVENT } from '~/neko/events'
|
||||||
|
|
||||||
import { accessor } from '~/store'
|
import { accessor } from '~/store'
|
||||||
|
|
||||||
@ -22,6 +22,12 @@ export const getters = getterTree(state, {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const mutations = mutationTree(state, {
|
export const mutations = mutationTree(state, {
|
||||||
|
setIgnored(state, { id, ignored }: { id: string; ignored: boolean }) {
|
||||||
|
state.members[id] = {
|
||||||
|
...state.members[id],
|
||||||
|
ignored,
|
||||||
|
}
|
||||||
|
},
|
||||||
setMuted(state, { id, muted }: { id: string; muted: boolean }) {
|
setMuted(state, { id, muted }: { id: string; muted: boolean }) {
|
||||||
state.members[id] = {
|
state.members[id] = {
|
||||||
...state.members[id],
|
...state.members[id],
|
||||||
@ -56,7 +62,7 @@ export const mutations = mutationTree(state, {
|
|||||||
connected: false,
|
connected: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearMembers(state) {
|
clear(state) {
|
||||||
state.members = {}
|
state.members = {}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,35 +1,21 @@
|
|||||||
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
|
||||||
import { accessor } from '~/store'
|
import { get, set } from '~/utils/localstorage'
|
||||||
|
|
||||||
export const namespaced = true
|
export const namespaced = true
|
||||||
|
|
||||||
export const state = () => {
|
export const state = () => ({
|
||||||
let volume = 100
|
index: -1,
|
||||||
let _volume = localStorage.getItem('volume')
|
tracks: [] as MediaStreamTrack[],
|
||||||
if (_volume) {
|
streams: [] as MediaStream[],
|
||||||
volume = parseInt(_volume)
|
width: 1280,
|
||||||
}
|
height: 720,
|
||||||
|
horizontal: 16,
|
||||||
let muted = false
|
vertical: 9,
|
||||||
let _muted = localStorage.getItem('muted')
|
volume: get<number>('volume', 100),
|
||||||
if (_muted) {
|
muted: get<boolean>('muted', false),
|
||||||
muted = _muted === '1'
|
playing: false,
|
||||||
}
|
playable: false,
|
||||||
|
})
|
||||||
return {
|
|
||||||
index: -1,
|
|
||||||
tracks: [] as MediaStreamTrack[],
|
|
||||||
streams: [] as MediaStream[],
|
|
||||||
width: 1280,
|
|
||||||
height: 720,
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 9,
|
|
||||||
volume,
|
|
||||||
muted,
|
|
||||||
playing: false,
|
|
||||||
playable: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getters = getterTree(state, {
|
export const getters = getterTree(state, {
|
||||||
stream: state => state.streams[state.index],
|
stream: state => state.streams[state.index],
|
||||||
@ -58,6 +44,7 @@ export const mutations = mutationTree(state, {
|
|||||||
|
|
||||||
toggleMute(state) {
|
toggleMute(state) {
|
||||||
state.muted = !state.muted
|
state.muted = !state.muted
|
||||||
|
set('mute', state.muted)
|
||||||
},
|
},
|
||||||
|
|
||||||
setPlayable(state, playable: boolean) {
|
setPlayable(state, playable: boolean) {
|
||||||
@ -107,7 +94,7 @@ export const mutations = mutationTree(state, {
|
|||||||
|
|
||||||
setVolume(state, volume: number) {
|
setVolume(state, volume: number) {
|
||||||
state.volume = volume
|
state.volume = volume
|
||||||
localStorage.setItem('volume', `${volume}`)
|
set('volume', volume)
|
||||||
},
|
},
|
||||||
|
|
||||||
setStream(state, index: number) {
|
setStream(state, index: number) {
|
||||||
@ -130,15 +117,3 @@ export const mutations = mutationTree(state, {
|
|||||||
state.streams = []
|
state.streams = []
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const actions = actionTree(
|
|
||||||
{ state, getters, mutations },
|
|
||||||
{
|
|
||||||
initialise({ commit }) {
|
|
||||||
const volume = localStorage.getItem('volume')
|
|
||||||
if (volume) {
|
|
||||||
accessor.video.setVolume(parseInt(volume))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
9
client/src/utils/index.ts
Normal file
9
client/src/utils/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function makeid(length: number) {
|
||||||
|
let result = ''
|
||||||
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
|
const charactersLength = characters.length
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
31
client/src/utils/localstorage.ts
Normal file
31
client/src/utils/localstorage.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export function set<T extends string | number | boolean>(key: string, val: T) {
|
||||||
|
switch (typeof val) {
|
||||||
|
case 'number':
|
||||||
|
localStorage.setItem(key, val.toString())
|
||||||
|
break
|
||||||
|
case 'string':
|
||||||
|
localStorage.setItem(key, val)
|
||||||
|
break
|
||||||
|
case 'boolean':
|
||||||
|
localStorage.setItem(key, val ? '1' : '0')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get<T extends string | number | boolean>(key: string, def: T): T {
|
||||||
|
let store = localStorage.getItem(key)
|
||||||
|
if (store) {
|
||||||
|
switch (typeof def) {
|
||||||
|
case 'number':
|
||||||
|
return parseInt(store) as T
|
||||||
|
case 'string':
|
||||||
|
return store as T
|
||||||
|
case 'boolean':
|
||||||
|
return (store === '1') as T
|
||||||
|
default:
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
@ -13,8 +13,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"types": [
|
"types": [
|
||||||
"webpack-env",
|
"webpack-env"
|
||||||
"w3c-image-capture"
|
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": [
|
"~/*": [
|
||||||
|
@ -16,9 +16,10 @@ const CONTROL_LOCKED = "control/locked"
|
|||||||
const CONTROL_RELEASE = "control/release"
|
const CONTROL_RELEASE = "control/release"
|
||||||
const CONTROL_REQUEST = "control/request"
|
const CONTROL_REQUEST = "control/request"
|
||||||
const CONTROL_REQUESTING = "control/requesting"
|
const CONTROL_REQUESTING = "control/requesting"
|
||||||
|
const CONTROL_GIVE = "control/give"
|
||||||
|
|
||||||
const CHAT_MESSAGE = "chat/message"
|
const CHAT_MESSAGE = "chat/message"
|
||||||
const CHAT_EMOJI = "chat/emoji"
|
const CHAT_EMOTE = "chat/emote"
|
||||||
|
|
||||||
const ADMIN_BAN = "admin/ban"
|
const ADMIN_BAN = "admin/ban"
|
||||||
const ADMIN_KICK = "admin/kick"
|
const ADMIN_KICK = "admin/kick"
|
||||||
@ -28,3 +29,4 @@ const ADMIN_UNLOCK = "admin/unlock"
|
|||||||
const ADMIN_UNMUTE = "admin/unmute"
|
const ADMIN_UNMUTE = "admin/unmute"
|
||||||
const ADMIN_CONTROL = "admin/control"
|
const ADMIN_CONTROL = "admin/control"
|
||||||
const ADMIN_RELEASE = "admin/release"
|
const ADMIN_RELEASE = "admin/release"
|
||||||
|
const ADMIN_GIVE = "admin/give"
|
||||||
|
@ -45,6 +45,12 @@ type Control struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ControlTarget struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
type ChatRecieve struct {
|
type ChatRecieve struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
@ -56,15 +62,15 @@ type ChatSend struct {
|
|||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmojiRecieve struct {
|
type EmoteRecieve struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
Emoji string `json:"emoji"`
|
Emote string `json:"emote"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmojiSend struct {
|
type EmoteSend struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Emoji string `json:"emoji"`
|
Emote string `json:"emote"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Admin struct {
|
type Admin struct {
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"n.eko.moe/neko/internal/event"
|
"n.eko.moe/neko/internal/event"
|
||||||
"n.eko.moe/neko/internal/message"
|
"n.eko.moe/neko/internal/message"
|
||||||
"n.eko.moe/neko/internal/session"
|
"n.eko.moe/neko/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *MessageHandler) adminLock(id string, session *session.Session) error {
|
func (h *MessageHandler) adminLock(id string, session *session.Session) error {
|
||||||
if !session.Admin || !h.locked {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.locked {
|
||||||
|
h.logger.Debug().Msg("server already locked...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +34,13 @@ func (h *MessageHandler) adminLock(id string, session *session.Session) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) adminUnlock(id string, session *session.Session) error {
|
func (h *MessageHandler) adminUnlock(id string, session *session.Session) error {
|
||||||
if !session.Admin || !h.locked {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.locked {
|
||||||
|
h.logger.Debug().Msg("server not locked...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +60,7 @@ func (h *MessageHandler) adminUnlock(id string, session *session.Session) error
|
|||||||
|
|
||||||
func (h *MessageHandler) adminControl(id string, session *session.Session) error {
|
func (h *MessageHandler) adminControl(id string, session *session.Session) error {
|
||||||
if !session.Admin {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +94,7 @@ func (h *MessageHandler) adminControl(id string, session *session.Session) error
|
|||||||
|
|
||||||
func (h *MessageHandler) adminRelease(id string, session *session.Session) error {
|
func (h *MessageHandler) adminRelease(id string, session *session.Session) error {
|
||||||
if !session.Admin {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,75 +126,28 @@ func (h *MessageHandler) adminRelease(id string, session *session.Session) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) adminBan(id string, session *session.Session, payload *message.Admin) error {
|
func (h *MessageHandler) adminGive(id string, session *session.Session, payload *message.Admin) error {
|
||||||
if !session.Admin {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
target, ok := h.sessions.Get(id)
|
if !h.sessions.Has(payload.ID) {
|
||||||
if !ok {
|
h.logger.Debug().Str("id", payload.ID).Msg("user does not exist")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.Admin {
|
// set host
|
||||||
return nil
|
h.sessions.SetHost(payload.ID)
|
||||||
}
|
|
||||||
|
|
||||||
address := target.RemoteAddr()
|
|
||||||
if address == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
h.banned[*address] = true
|
|
||||||
|
|
||||||
if err := session.Kick(message.Disconnect{
|
|
||||||
Event: event.SYSTEM_DISCONNECT,
|
|
||||||
Message: "You have been banned",
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// let everyone know
|
||||||
if err := h.sessions.Brodcast(
|
if err := h.sessions.Brodcast(
|
||||||
message.AdminTarget{
|
message.AdminTarget{
|
||||||
Event: event.ADMIN_BAN,
|
Event: event.CONTROL_GIVE,
|
||||||
Target: target.ID,
|
|
||||||
ID: id,
|
ID: id,
|
||||||
|
Target: payload.ID,
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.ADMIN_BAN)
|
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.CONTROL_LOCKED)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *MessageHandler) adminKick(id string, session *session.Session, payload *message.Admin) error {
|
|
||||||
if !session.Admin {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
target, ok := h.sessions.Get(payload.ID)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if target.Admin {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := target.Kick(message.Disconnect{
|
|
||||||
Event: event.SYSTEM_DISCONNECT,
|
|
||||||
Message: "You have been kicked",
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.sessions.Brodcast(
|
|
||||||
message.AdminTarget{
|
|
||||||
Event: event.ADMIN_KICK,
|
|
||||||
Target: target.ID,
|
|
||||||
ID: id,
|
|
||||||
}, nil); err != nil {
|
|
||||||
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.ADMIN_KICK)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,15 +156,18 @@ func (h *MessageHandler) adminKick(id string, session *session.Session, payload
|
|||||||
|
|
||||||
func (h *MessageHandler) adminMute(id string, session *session.Session, payload *message.Admin) error {
|
func (h *MessageHandler) adminMute(id string, session *session.Session, payload *message.Admin) error {
|
||||||
if !session.Admin {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
target, ok := h.sessions.Get(payload.ID)
|
target, ok := h.sessions.Get(payload.ID)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
h.logger.Debug().Str("id", payload.ID).Msg("can't find session id")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if target.Admin {
|
if target.Admin {
|
||||||
|
h.logger.Debug().Msg("target is an admin, baling")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,11 +188,13 @@ func (h *MessageHandler) adminMute(id string, session *session.Session, payload
|
|||||||
|
|
||||||
func (h *MessageHandler) adminUnmute(id string, session *session.Session, payload *message.Admin) error {
|
func (h *MessageHandler) adminUnmute(id string, session *session.Session, payload *message.Admin) error {
|
||||||
if !session.Admin {
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
target, ok := h.sessions.Get(payload.ID)
|
target, ok := h.sessions.Get(payload.ID)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
h.logger.Debug().Str("id", payload.ID).Msg("can't find target session")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,3 +212,93 @@ func (h *MessageHandler) adminUnmute(id string, session *session.Session, payloa
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) adminKick(id string, session *session.Session, payload *message.Admin) error {
|
||||||
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
target, ok := h.sessions.Get(payload.ID)
|
||||||
|
if !ok {
|
||||||
|
h.logger.Debug().Str("id", payload.ID).Msg("can't find session id")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Admin {
|
||||||
|
h.logger.Debug().Msg("target is an admin, baling")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := target.Kick(message.Disconnect{
|
||||||
|
Event: event.SYSTEM_DISCONNECT,
|
||||||
|
Message: "You have been kicked",
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.sessions.Brodcast(
|
||||||
|
message.AdminTarget{
|
||||||
|
Event: event.ADMIN_KICK,
|
||||||
|
Target: target.ID,
|
||||||
|
ID: id,
|
||||||
|
}, []string{payload.ID}); err != nil {
|
||||||
|
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.ADMIN_KICK)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) adminBan(id string, session *session.Session, payload *message.Admin) error {
|
||||||
|
if !session.Admin {
|
||||||
|
h.logger.Debug().Msg("user not admin")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
target, ok := h.sessions.Get(payload.ID)
|
||||||
|
if !ok {
|
||||||
|
h.logger.Debug().Str("id", payload.ID).Msg("can't find session id")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Admin {
|
||||||
|
h.logger.Debug().Msg("target is an admin, baling")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
remote := target.RemoteAddr()
|
||||||
|
if remote == nil {
|
||||||
|
h.logger.Debug().Msg("no remote address, baling")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
address := strings.SplitN(*remote, ":", -1)
|
||||||
|
if len(address[0]) < 1 {
|
||||||
|
h.logger.Debug().Str("address", *remote).Msg("no remote address, baling")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug().Str("address", *remote).Msg("adding address to banned")
|
||||||
|
|
||||||
|
h.banned[address[0]] = true
|
||||||
|
|
||||||
|
if err := target.Kick(message.Disconnect{
|
||||||
|
Event: event.SYSTEM_DISCONNECT,
|
||||||
|
Message: "You have been banned",
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.sessions.Brodcast(
|
||||||
|
message.AdminTarget{
|
||||||
|
Event: event.ADMIN_BAN,
|
||||||
|
Target: target.ID,
|
||||||
|
ID: id,
|
||||||
|
}, []string{payload.ID}); err != nil {
|
||||||
|
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.ADMIN_BAN)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -23,15 +23,15 @@ func (h *MessageHandler) chat(id string, session *session.Session, payload *mess
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) chatEmoji(id string, session *session.Session, payload *message.EmojiRecieve) error {
|
func (h *MessageHandler) chatEmote(id string, session *session.Session, payload *message.EmoteRecieve) error {
|
||||||
if session.Muted {
|
if session.Muted {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.sessions.Brodcast(
|
if err := h.sessions.Brodcast(
|
||||||
message.EmojiSend{
|
message.EmoteSend{
|
||||||
Event: event.CHAT_MESSAGE,
|
Event: event.CHAT_EMOTE,
|
||||||
Emoji: payload.Emoji,
|
Emote: payload.Emote,
|
||||||
ID: id,
|
ID: id,
|
||||||
}, nil); err != nil {
|
}, nil); err != nil {
|
||||||
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.CONTROL_RELEASE)
|
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.CONTROL_RELEASE)
|
||||||
|
@ -10,6 +10,7 @@ func (h *MessageHandler) controlRelease(id string, session *session.Session) err
|
|||||||
|
|
||||||
// check if session is host
|
// check if session is host
|
||||||
if !h.sessions.IsHost(id) {
|
if !h.sessions.IsHost(id) {
|
||||||
|
h.logger.Debug().Str("id", id).Msg("is not the host")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,8 +32,6 @@ func (h *MessageHandler) controlRelease(id string, session *session.Session) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) controlRequest(id string, session *session.Session) error {
|
func (h *MessageHandler) controlRequest(id string, session *session.Session) error {
|
||||||
h.logger.Debug().Str("id", id).Msgf("user called %s", event.CONTROL_REQUEST)
|
|
||||||
|
|
||||||
// check for host
|
// check for host
|
||||||
if !h.sessions.HasHost() {
|
if !h.sessions.HasHost() {
|
||||||
// set host
|
// set host
|
||||||
@ -76,3 +75,32 @@ func (h *MessageHandler) controlRequest(id string, session *session.Session) err
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) controlGive(id string, session *session.Session, payload *message.Control) error {
|
||||||
|
// check if session is host
|
||||||
|
if !h.sessions.IsHost(id) {
|
||||||
|
h.logger.Debug().Str("id", id).Msg("is not the host")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.sessions.Has(payload.ID) {
|
||||||
|
h.logger.Debug().Str("id", payload.ID).Msg("user does not exist")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// set host
|
||||||
|
h.sessions.SetHost(payload.ID)
|
||||||
|
|
||||||
|
// let everyone know
|
||||||
|
if err := h.sessions.Brodcast(
|
||||||
|
message.ControlTarget{
|
||||||
|
Event: event.CONTROL_GIVE,
|
||||||
|
ID: id,
|
||||||
|
Target: payload.ID,
|
||||||
|
}, nil); err != nil {
|
||||||
|
h.logger.Warn().Err(err).Msgf("brodcasting event %s has failed", event.CONTROL_LOCKED)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -223,7 +223,6 @@ func (ws *WebSocketHandler) handle(socket *websocket.Conn, id string) {
|
|||||||
Msg("recieved message from client")
|
Msg("recieved message from client")
|
||||||
if err := ws.handler.Message(id, raw); err != nil {
|
if err := ws.handler.Message(id, raw); err != nil {
|
||||||
ws.logger.Error().Err(err).Msg("message handler has failed")
|
ws.logger.Error().Err(err).Msg("message handler has failed")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
case <-cancel:
|
case <-cancel:
|
||||||
return
|
return
|
||||||
|
@ -2,6 +2,7 @@ package websocket
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -23,13 +24,24 @@ type MessageHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MessageHandler) SocketConnected(id string, socket *websocket.Conn) (bool, string, error) {
|
func (h *MessageHandler) SocketConnected(id string, socket *websocket.Conn) (bool, string, error) {
|
||||||
ok, banned := h.banned[socket.RemoteAddr().String()]
|
remote := socket.RemoteAddr().String()
|
||||||
if ok && banned {
|
if remote != "" {
|
||||||
return false, "you are banned", nil
|
address := strings.SplitN(remote, ":", -1)
|
||||||
|
if len(address[0]) < 1 {
|
||||||
|
h.logger.Debug().Str("address", remote).Msg("no remote address, baling")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
ok, banned := h.banned[address[0]]
|
||||||
|
if ok && banned {
|
||||||
|
h.logger.Debug().Str("address", remote).Msg("banned")
|
||||||
|
return false, "This IP has been banned", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.locked {
|
if h.locked {
|
||||||
return false, "stream is currently locked", nil
|
h.logger.Debug().Str("address", remote).Msg("locked")
|
||||||
|
return false, "Server is currently locked", nil
|
||||||
}
|
}
|
||||||
return true, "", nil
|
return true, "", nil
|
||||||
}
|
}
|
||||||
@ -71,6 +83,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
return errors.Wrapf(h.controlRelease(id, session), "%s failed", header.Event)
|
return errors.Wrapf(h.controlRelease(id, session), "%s failed", header.Event)
|
||||||
case event.CONTROL_REQUEST:
|
case event.CONTROL_REQUEST:
|
||||||
return errors.Wrapf(h.controlRequest(id, session), "%s failed", header.Event)
|
return errors.Wrapf(h.controlRequest(id, session), "%s failed", header.Event)
|
||||||
|
case event.CONTROL_GIVE:
|
||||||
|
payload := &message.Control{}
|
||||||
|
return errors.Wrapf(
|
||||||
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
|
return h.controlGive(id, session, payload)
|
||||||
|
}), "%s failed", header.Event)
|
||||||
|
|
||||||
// Chat Events
|
// Chat Events
|
||||||
case event.CHAT_MESSAGE:
|
case event.CHAT_MESSAGE:
|
||||||
@ -79,11 +97,11 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
utils.Unmarshal(payload, raw, func() error {
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
return h.chat(id, session, payload)
|
return h.chat(id, session, payload)
|
||||||
}), "%s failed", header.Event)
|
}), "%s failed", header.Event)
|
||||||
case event.CHAT_EMOJI:
|
case event.CHAT_EMOTE:
|
||||||
payload := &message.EmojiRecieve{}
|
payload := &message.EmoteRecieve{}
|
||||||
return errors.Wrapf(
|
return errors.Wrapf(
|
||||||
utils.Unmarshal(payload, raw, func() error {
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
return h.chatEmoji(id, session, payload)
|
return h.chatEmote(id, session, payload)
|
||||||
}), "%s failed", header.Event)
|
}), "%s failed", header.Event)
|
||||||
|
|
||||||
// Admin Events
|
// Admin Events
|
||||||
@ -95,6 +113,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
return errors.Wrapf(h.adminControl(id, session), "%s failed", header.Event)
|
return errors.Wrapf(h.adminControl(id, session), "%s failed", header.Event)
|
||||||
case event.ADMIN_RELEASE:
|
case event.ADMIN_RELEASE:
|
||||||
return errors.Wrapf(h.adminRelease(id, session), "%s failed", header.Event)
|
return errors.Wrapf(h.adminRelease(id, session), "%s failed", header.Event)
|
||||||
|
case event.ADMIN_GIVE:
|
||||||
|
payload := &message.Admin{}
|
||||||
|
return errors.Wrapf(
|
||||||
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
|
return h.adminGive(id, session, payload)
|
||||||
|
}), "%s failed", header.Event)
|
||||||
case event.ADMIN_BAN:
|
case event.ADMIN_BAN:
|
||||||
payload := &message.Admin{}
|
payload := &message.Admin{}
|
||||||
return errors.Wrapf(
|
return errors.Wrapf(
|
||||||
|
Reference in New Issue
Block a user