diff --git a/README.md b/README.md index dd1933a3..89137acc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ I like cats (Neko is the Japanese word for cat), I'm a weeb/nerd, I own the doma ### Super easy mode setup 1. Deploy a Server/VPS - *Recomended Specs:* + *Recomended Specs:* (Note: these may not be correct, I did a small round testing, 4c/4gb worked fine with small hickups here and there) | Resolution | Cores | Ram | Recommendation | |------------|-------|-------|------------------| diff --git a/client/.eslintrc b/client/.eslintrc index a52d45af..3bb64d6a 100644 --- a/client/.eslintrc +++ b/client/.eslintrc @@ -8,6 +8,7 @@ "parser": "@typescript-eslint/parser" }, "rules": { + "vue/valid-v-for": "off", "no-case-declarations": "off", "no-dupe-class-members": "off", "no-console": "off", diff --git a/client/package.json b/client/package.json index dd96b97e..3a81623e 100644 --- a/client/package.json +++ b/client/package.json @@ -19,21 +19,35 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^5.12.0", + "animejs": "^3.1.0", + "axios": "^0.19.1", + "bulma": "^0.8.0", + "date-fns": "^2.9.0", "eventemitter3": "^4.0.0", + "resize-observer-polyfill": "^1.5.1", + "simple-markdown": "^0.7.2", + "sweetalert2": "^9.6.1", + "typed-vuex": "^0.1.15", + "v-tooltip": "^2.0.3", "vue": "^2.6.10", "vue-class-component": "^7.0.2", + "vue-context": "^5.0.0", "vue-notification": "^1.3.20", "vue-property-decorator": "^8.3.0", - "typed-vuex": "^0.1.15", + "vue-router": "^3.1.5", "vuex": "^3.1.2" }, "devDependencies": { + "@types/animejs": "^3.1.0", + "@types/vue": "^2.0.0", + "@types/w3c-image-capture": "^1.0.2", "@vue/cli-plugin-eslint": "^4.1.0", "@vue/cli-plugin-typescript": "^4.1.0", "@vue/cli-plugin-vuex": "^4.1.0", "@vue/cli-service": "^4.1.0", "@vue/eslint-config-prettier": "^5.0.0", "@vue/eslint-config-typescript": "^4.0.0", + "autoprefixer": "^9.7.4", "eslint": "^5.16.0", "eslint-plugin-prettier": "^3.1.1", "eslint-plugin-vue": "^5.0.0", diff --git a/client/src/App.vue b/client/src/App.vue deleted file mode 100644 index cc28c999..00000000 --- a/client/src/App.vue +++ /dev/null @@ -1,571 +0,0 @@ - - - - - diff --git a/client/src/app.vue b/client/src/app.vue new file mode 100644 index 00000000..47f23fc7 --- /dev/null +++ b/client/src/app.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/client/src/assets/styles/_variables.scss b/client/src/assets/styles/_variables.scss index 82490f3c..d22add50 100644 --- a/client/src/assets/styles/_variables.scss +++ b/client/src/assets/styles/_variables.scss @@ -1,10 +1,31 @@ +$text-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;; +$text-size: 14px; +$text-normal: #dcddde; +$text-muted: #72767d; +$text-link: #00b0f4; +$interactive-normal: #b9bbbe; +$interactive-hover: #dcddde; +$interactive-active: #fff; +$interactive-muted: #4f545c; + +$background-primary: #36393f; +$background-secondary: #2f3136; +$background-tertiary: #202225; +$background-accent: #4f545c; +$background-floating: #18191c; +$background-modifier-hover: rgba(79, 84, 92, 0.16); +$background-modifier-active: rgba(79, 84, 92, 0.24); +$background-modifier-selected: rgba(79, 84, 92, 0.32); +$background-modifier-accent: hsla(0, 0%, 100%, 0.06); + +$elevation-low: 0 1px 0 rgba(4, 4, 5, 0.2), 0 1.5px 0 rgba(6, 6, 7, 0.05), 0 2px 0 rgba(4, 4, 5, 0.05); +$elevation-high: 0 8px 16px rgba(0, 0, 0, 0.24); -$style-dark: #2c2c2c; -$style-darker: #1a1a1a; -$style-light: #fafafa; $style-primary: #19bd9c; +$style-error: #d32f2f; + +$menu-height: 40px; +$controls-height: 125px; +$side-width: 300px; -$style-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; -$style-font-color: $style-dark; -$style-font-size: 14px; diff --git a/client/src/assets/styles/fonts/whitney-300.woff b/client/src/assets/styles/fonts/whitney-300.woff new file mode 100644 index 00000000..dc914270 Binary files /dev/null and b/client/src/assets/styles/fonts/whitney-300.woff differ diff --git a/client/src/assets/styles/fonts/whitney-400.woff b/client/src/assets/styles/fonts/whitney-400.woff new file mode 100644 index 00000000..2b330815 Binary files /dev/null and b/client/src/assets/styles/fonts/whitney-400.woff differ diff --git a/client/src/assets/styles/fonts/whitney-500.woff b/client/src/assets/styles/fonts/whitney-500.woff new file mode 100644 index 00000000..fc821385 Binary files /dev/null and b/client/src/assets/styles/fonts/whitney-500.woff differ diff --git a/client/src/assets/styles/fonts/whitney-600.woff b/client/src/assets/styles/fonts/whitney-600.woff new file mode 100644 index 00000000..598a6f8c Binary files /dev/null and b/client/src/assets/styles/fonts/whitney-600.woff differ diff --git a/client/src/assets/styles/fonts/whitney-700.woff b/client/src/assets/styles/fonts/whitney-700.woff new file mode 100644 index 00000000..50565a3e Binary files /dev/null and b/client/src/assets/styles/fonts/whitney-700.woff differ diff --git a/client/src/assets/styles/main.scss b/client/src/assets/styles/main.scss index 6a321ad3..15826306 100644 --- a/client/src/assets/styles/main.scss +++ b/client/src/assets/styles/main.scss @@ -8,16 +8,20 @@ // Import Vendor @import "vendor/font-awesome"; +@import "vendor/font-whitney"; +@import "vendor/swal"; +@import "vendor/tooltip"; +@import "vendor/github"; +// @import "vendor/bulma"; html, body { -webkit-font-smoothing: subpixel-antialiased; - background-color: $style-dark; - font-family: $style-font-family; - font-size: $style-font-size; - color: $style-font-color; + background-color: $background-tertiary; + font-family: $text-family; + font-size: $text-size; + color: $text-normal; overflow: hidden; width: 100vw; height: 100vh; min-width: 320px; -} - +} \ No newline at end of file diff --git a/client/src/assets/styles/vendor/_bulma.scss b/client/src/assets/styles/vendor/_bulma.scss new file mode 100644 index 00000000..59c05ede --- /dev/null +++ b/client/src/assets/styles/vendor/_bulma.scss @@ -0,0 +1,245 @@ +@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"; \ No newline at end of file diff --git a/client/src/assets/styles/vendor/_font-whitney.scss b/client/src/assets/styles/vendor/_font-whitney.scss new file mode 100644 index 00000000..97baab9f --- /dev/null +++ b/client/src/assets/styles/vendor/_font-whitney.scss @@ -0,0 +1,29 @@ +@font-face{ + font-family:Whitney; + font-weight:300; + src:url("fonts/whitney-300.woff") format("woff") +} + +@font-face{ + font-family:Whitney; + font-weight:400; + src:url("fonts/whitney-400.woff") format("woff") +} + +@font-face{ + font-family:Whitney; + font-weight:500; + src:url("fonts/whitney-500.woff") format("woff") +} + +@font-face{ + font-family:Whitney; + font-weight:600; + src:url("fonts/whitney-600.woff") format("woff") +} + +@font-face{ + font-family:Whitney; + font-weight:700; + src:url("fonts/whitney-700.woff") format("woff") +} diff --git a/client/src/assets/styles/vendor/_github.scss b/client/src/assets/styles/vendor/_github.scss new file mode 100644 index 00000000..f05204f1 --- /dev/null +++ b/client/src/assets/styles/vendor/_github.scss @@ -0,0 +1,957 @@ +@font-face { + font-family: octicons-link; + src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); +} + +.markdown-body .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-body .anchor { + float: left; + line-height: 1; + margin-left: -20px; + padding-right: 4px; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body h1 .octicon-link, +.markdown-body h2 .octicon-link, +.markdown-body h3 .octicon-link, +.markdown-body h4 .octicon-link, +.markdown-body h5 .octicon-link, +.markdown-body h6 .octicon-link { + color: $text-normal; + vertical-align: middle; + visibility: hidden; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body h1:hover .anchor .octicon-link, +.markdown-body h2:hover .anchor .octicon-link, +.markdown-body h3:hover .anchor .octicon-link, +.markdown-body h4:hover .anchor .octicon-link, +.markdown-body h5:hover .anchor .octicon-link, +.markdown-body h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + color: $text-normal; + line-height: 1.5; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .pl-c { + color: #6a737d; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #005cc5; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6f42c1; +} + +.markdown-body .pl-s .pl-s1, +.markdown-body .pl-smi { + color: #24292e; +} + +.markdown-body .pl-ent { + color: #22863a; +} + +.markdown-body .pl-k { + color: #d73a49; +} + +.markdown-body .pl-pds, +.markdown-body .pl-s, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sra, +.markdown-body .pl-sr .pl-sre { + color: #032f62; +} + +.markdown-body .pl-smw, +.markdown-body .pl-v { + color: #e36209; +} + +.markdown-body .pl-bu { + color: #b31d28; +} + +.markdown-body .pl-ii { + background-color: #b31d28; + color: #fafbfc; +} + +.markdown-body .pl-c2 { + background-color: #d73a49; + color: #fafbfc; +} + +.markdown-body .pl-c2:before { + content: "^M"; +} + +.markdown-body .pl-sr .pl-cce { + color: #22863a; + font-weight: 700; +} + +.markdown-body .pl-ml { + color: #735c0f; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + color: #005cc5; + font-weight: 700; +} + +.markdown-body .pl-mi { + color: #24292e; + font-style: italic; +} + +.markdown-body .pl-mb { + color: #24292e; + font-weight: 700; +} + +.markdown-body .pl-md { + background-color: #ffeef0; + color: #b31d28; +} + +.markdown-body .pl-mi1 { + background-color: #f0fff4; + color: #22863a; +} + +.markdown-body .pl-mc { + background-color: #ffebda; + color: #e36209; +} + +.markdown-body .pl-mi2 { + background-color: #005cc5; + color: #f6f8fa; +} + +.markdown-body .pl-mdr { + color: #6f42c1; + font-weight: 700; +} + +.markdown-body .pl-ba { + color: #586069; +} + +.markdown-body .pl-sg { + color: #959da5; +} + +.markdown-body .pl-corl { + color: #032f62; + text-decoration: underline; +} + +.markdown-body details { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body a { + background-color: transparent; +} + +.markdown-body a:active, +.markdown-body a:hover { + outline-width: 0; +} + +.markdown-body strong { + font-weight: inherit; + font-weight: bolder; +} + +.markdown-body h1 { + font-size: 2em; + margin: .67em 0; +} + +.markdown-body img { + border-style: none; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre { + font-family: monospace,monospace; + font-size: 1em; +} + +.markdown-body hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +.markdown-body input { + font: inherit; + margin: 0; +} + +.markdown-body input { + overflow: visible; +} + +.markdown-body [type=checkbox] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body * { + box-sizing: border-box; +} + +.markdown-body input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body a { + color: $text-link; + text-decoration: none; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body strong { + font-weight: 600; +} + +.markdown-body hr { + background: transparent; + border: 0; + border-bottom: 1px solid $background-primary; + height: 0; + margin: 15px 0; + overflow: hidden; +} + +.markdown-body hr:before { + content: ""; + display: table; +} + +.markdown-body hr:after { + clear: both; + content: ""; + display: table; +} + +.markdown-body table { + border-collapse: collapse; + border-spacing: 0; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-bottom: 0; + margin-top: 0; +} + +.markdown-body h1 { + font-size: 32px; +} + +.markdown-body h1, +.markdown-body h2 { + font-weight: 600; +} + +.markdown-body h2 { + font-size: 24px; +} + +.markdown-body h3 { + font-size: 20px; +} + +.markdown-body h3, +.markdown-body h4 { + font-weight: 600; +} + +.markdown-body h4 { + font-size: 16px; +} + +.markdown-body h5 { + font-size: 14px; +} + +.markdown-body h5, +.markdown-body h6 { + font-weight: 600; +} + +.markdown-body h6 { + font-size: 12px; +} + +.markdown-body p { + margin-bottom: 10px; + margin-top: 0; +} + +.markdown-body blockquote { + margin: 0; +} + +.markdown-body ol, +.markdown-body ul { + margin-bottom: 0; + margin-top: 0; + padding-left: 0; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ol ol ol, +.markdown-body ol ul ol, +.markdown-body ul ol ol, +.markdown-body ul ul ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body code, +.markdown-body pre { + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-bottom: 0; + margin-top: 0; +} + +.markdown-body input::-webkit-inner-spin-button, +.markdown-body input::-webkit-outer-spin-button { + -webkit-appearance: none; + appearance: none; + margin: 0; +} + +.markdown-body .border { + border: 1px solid #e1e4e8!important; +} + +.markdown-body .border-0 { + border: 0!important; +} + +.markdown-body .border-bottom { + border-bottom: 1px solid #e1e4e8!important; +} + +.markdown-body .rounded-1 { + border-radius: 3px!important; +} + +.markdown-body .bg-white { + background-color: #fff!important; +} + +.markdown-body .bg-gray-light { + background-color: #fafbfc!important; +} + +.markdown-body .text-gray-light { + color: $text-normal!important; +} + +.markdown-body .mb-0 { + margin-bottom: 0!important; +} + +.markdown-body .my-2 { + margin-bottom: 8px!important; + margin-top: 8px!important; +} + +.markdown-body .pl-0 { + padding-left: 0!important; +} + +.markdown-body .py-0 { + padding-bottom: 0!important; + padding-top: 0!important; +} + +.markdown-body .pl-1 { + padding-left: 4px!important; +} + +.markdown-body .pl-2 { + padding-left: 8px!important; +} + +.markdown-body .py-2 { + padding-bottom: 8px!important; + padding-top: 8px!important; +} + +.markdown-body .pl-3, +.markdown-body .px-3 { + padding-left: 16px!important; +} + +.markdown-body .px-3 { + padding-right: 16px!important; +} + +.markdown-body .pl-4 { + padding-left: 24px!important; +} + +.markdown-body .pl-5 { + padding-left: 32px!important; +} + +.markdown-body .pl-6 { + padding-left: 40px!important; +} + +.markdown-body .f6 { + font-size: 12px!important; +} + +.markdown-body .lh-condensed { + line-height: 1.25!important; +} + +.markdown-body .text-bold { + font-weight: 600!important; +} + +.markdown-body:before { + content: ""; + display: table; +} + +.markdown-body:after { + clear: both; + content: ""; + display: table; +} + +.markdown-body>:first-child { + margin-top: 0!important; +} + +.markdown-body>:last-child { + margin-bottom: 0!important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body blockquote, +.markdown-body dl, +.markdown-body ol, +.markdown-body p, +.markdown-body pre, +.markdown-body table, +.markdown-body ul { + margin-bottom: 16px; + margin-top: 0; +} + +.markdown-body hr { + background-color: #e1e4e8; + border: 0; + height: .25em; + margin: 24px 0; + padding: 0; +} + +.markdown-body blockquote { + background-color: $background-primary; + border-left: .25em solid $style-primary; + color: $text-normal; + padding: 0 1em; +} + +.markdown-body blockquote>:first-child { + margin-top: 0; +} + +.markdown-body blockquote>:last-child { + margin-bottom: 0; +} + +.markdown-body kbd { + background-color: #fafbfc; + border: 1px solid #c6cbd1; + border-bottom-color: #959da5; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #959da5; + color: #444d56; + display: inline-block; + font-size: 11px; + line-height: 10px; + padding: 3px 5px; + vertical-align: middle; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + font-weight: 600; + line-height: 1.25; + margin-bottom: 16px; + margin-top: 24px; +} + +.markdown-body h1 { + font-size: 2em; +} + +.markdown-body h1, +.markdown-body h2 { + border-bottom: 1px solid #eaecef; + padding-bottom: .3em; +} + +.markdown-body h2 { + font-size: 1.5em; +} + +.markdown-body h3 { + font-size: 1.25em; +} + +.markdown-body h4 { + font-size: 1em; +} + +.markdown-body h5 { + font-size: .875em; +} + +.markdown-body h6 { + color: #6a737d; + font-size: .85em; +} + +.markdown-body ol, +.markdown-body ul { + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ol ul, +.markdown-body ul ol, +.markdown-body ul ul { + margin-bottom: 0; + margin-top: 0; +} + +.markdown-body li { + word-wrap: break-all; +} + +.markdown-body li>p { + margin-top: 16px; +} + +.markdown-body li+li { + margin-top: .25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + font-size: 1em; + font-style: italic; + font-weight: 600; + margin-top: 16px; + padding: 0; +} + +.markdown-body dl dd { + margin-bottom: 16px; + padding: 0 16px; +} + +.markdown-body table { + display: block; + overflow: auto; + width: 100%; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table td, +.markdown-body table th { + border: 1px solid $background-floating; + padding: 6px 13px; +} + +.markdown-body table tr { + background-color: $background-primary; + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tr:nth-child(2n) { + background-color: $background-secondary; +} + +.markdown-body img { + box-sizing: content-box; + max-width: 100%; +} + +.markdown-body img[align=right] { + padding-left: 20px; +} + +.markdown-body img[align=left] { + padding-right: 20px; +} + +.markdown-body code { + background-color: rgba(27,31,35,.05); + border-radius: 3px; + font-size: 85%; + margin: 0; + padding: .2em .4em; +} + +.markdown-body pre { + word-wrap: normal; +} + +.markdown-body pre>code { + background: transparent; + border: 0; + font-size: 100%; + margin: 0; + padding: 0; + white-space: pre; + word-break: normal; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + background-color: $background-primary; + border-radius: 3px; + font-size: 85%; + line-height: 1.45; + overflow: auto; + padding: 16px; +} + +.markdown-body pre code { + background-color: transparent; + border: 0; + display: inline; + line-height: inherit; + margin: 0; + max-width: auto; + overflow: visible; + padding: 0; + word-wrap: normal; +} + +.markdown-body .commit-tease-sha { + color: #444d56; + display: inline-block; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; + font-size: 90%; +} + +.markdown-body .blob-wrapper { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + overflow-x: auto; + overflow-y: hidden; +} + +.markdown-body .blob-wrapper-embedded { + max-height: 240px; + overflow-y: auto; +} + +.markdown-body .blob-num { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + color: rgba(27,31,35,.3); + cursor: pointer; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; + font-size: 12px; + line-height: 20px; + min-width: 50px; + padding-left: 10px; + padding-right: 10px; + text-align: right; + user-select: none; + vertical-align: top; + white-space: nowrap; + width: 1%; +} + +.markdown-body .blob-num:hover { + color: rgba(27,31,35,.6); +} + +.markdown-body .blob-num:before { + content: attr(data-line-number); +} + +.markdown-body .blob-code { + line-height: 20px; + padding-left: 10px; + padding-right: 10px; + position: relative; + vertical-align: top; +} + +.markdown-body .blob-code-inner { + color: #24292e; + font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; + font-size: 12px; + overflow: visible; + white-space: pre; + word-wrap: normal; +} + +.markdown-body .pl-token.active, +.markdown-body .pl-token:hover { + background: #ffea7f; + cursor: pointer; +} + +.markdown-body kbd { + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-bottom-color: #c6cbd1; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #c6cbd1; + color: #444d56; + display: inline-block; + font: 11px SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; + line-height: 10px; + padding: 3px 5px; + vertical-align: middle; +} + +.markdown-body :checked+.radio-label { + border-color: #0366d6; + position: relative; + z-index: 1; +} + +.markdown-body .tab-size[data-tab-size="1"] { + -moz-tab-size: 1; + tab-size: 1; +} + +.markdown-body .tab-size[data-tab-size="2"] { + -moz-tab-size: 2; + tab-size: 2; +} + +.markdown-body .tab-size[data-tab-size="3"] { + -moz-tab-size: 3; + tab-size: 3; +} + +.markdown-body .tab-size[data-tab-size="4"] { + -moz-tab-size: 4; + tab-size: 4; +} + +.markdown-body .tab-size[data-tab-size="5"] { + -moz-tab-size: 5; + tab-size: 5; +} + +.markdown-body .tab-size[data-tab-size="6"] { + -moz-tab-size: 6; + tab-size: 6; +} + +.markdown-body .tab-size[data-tab-size="7"] { + -moz-tab-size: 7; + tab-size: 7; +} + +.markdown-body .tab-size[data-tab-size="8"] { + -moz-tab-size: 8; + tab-size: 8; +} + +.markdown-body .tab-size[data-tab-size="9"] { + -moz-tab-size: 9; + tab-size: 9; +} + +.markdown-body .tab-size[data-tab-size="10"] { + -moz-tab-size: 10; + tab-size: 10; +} + +.markdown-body .tab-size[data-tab-size="11"] { + -moz-tab-size: 11; + tab-size: 11; +} + +.markdown-body .tab-size[data-tab-size="12"] { + -moz-tab-size: 12; + tab-size: 12; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item+.task-list-item { + margin-top: 3px; +} + +.markdown-body .task-list-item input { + margin: 0 .2em .25em -1.6em; + vertical-align: middle; +} + +.markdown-body hr { + border-bottom-color: #eee; +} + +.markdown-body .pl-0 { + padding-left: 0!important; +} + +.markdown-body .pl-1 { + padding-left: 4px!important; +} + +.markdown-body .pl-2 { + padding-left: 8px!important; +} + +.markdown-body .pl-3 { + padding-left: 16px!important; +} + +.markdown-body .pl-4 { + padding-left: 24px!important; +} + +.markdown-body .pl-5 { + padding-left: 32px!important; +} + +.markdown-body .pl-6 { + padding-left: 40px!important; +} + +.markdown-body .pl-7 { + padding-left: 48px!important; +} + +.markdown-body .pl-8 { + padding-left: 64px!important; +} + +.markdown-body .pl-9 { + padding-left: 80px!important; +} + +.markdown-body .pl-10 { + padding-left: 96px!important; +} + +.markdown-body .pl-11 { + padding-left: 112px!important; +} + +.markdown-body .pl-12 { + padding-left: 128px!important; +} \ No newline at end of file diff --git a/client/src/assets/styles/vendor/_swal.scss b/client/src/assets/styles/vendor/_swal.scss new file mode 100644 index 00000000..5481573b --- /dev/null +++ b/client/src/assets/styles/vendor/_swal.scss @@ -0,0 +1,188 @@ +$swal2-white: #fff; +$swal2-black: #000; +$swal2-outline-color: transparent; + +// CONTAINER +$swal2-container-padding: .625em; + +// BOX MODEL +$swal2-width: 32em; +$swal2-padding: 1.25em; +$swal2-border: none; +$swal2-border-radius: .3125em; +$swal2-box-shadow: #d9d9d9; + +// ANIMATIONS +$swal2-show-animation: swal2-show .3s; +$swal2-hide-animation: swal2-hide .15s forwards; + +// BACKGROUND +$swal2-background: $background-secondary; + +// TYPOGRAPHY +$swal2-font: inherit; +$swal2-font-size: 1rem; + +// BACKDROP +$swal2-backdrop: rgba($swal2-black, .4); +$swal2-backdrop-transition: background-color .1s; + +// ICONS +$swal2-icon-size: 5em; +$swal2-icon-animations: true; +$swal2-icon-margin: 1.25em auto 1.875em; +$swal2-icon-zoom: null; +$swal2-success: #a5dc86; +$swal2-success-border: rgba($swal2-success, .3); +$swal2-error: #f27474; +$swal2-warning: #f8bb86; +$swal2-info: #3fc3ee; +$swal2-question: #87adbd; +$swal2-icon-font-family: inherit; + +// IMAGE +$swal2-image-margin: 1.25em auto; + +// TITLE +$swal2-title-margin: 0 0 .4em; +$swal2-title-color: $interactive-hover; +$swal2-title-font-size: 1.875em; + +// CONTENT +$swal2-content-justify-content: center; +$swal2-content-margin: 0; +$swal2-content-pading: 0; +$swal2-content-color: $interactive-hover; +$swal2-content-font-size: 1.125em; +$swal2-content-font-weight: normal; +$swal2-content-line-height: normal; +$swal2-content-text-align: center; +$swal2-content-word-wrap: break-word; + +// INPUT +$swal2-input-margin: 1em auto; +$swal2-input-width: 100%; +$swal2-input-height: 2.625em; +$swal2-input-padding: 0 .75em; +$swal2-input-border: 1px solid lighten($swal2-black, 85); +$swal2-input-border-radius: .1875em; +$swal2-input-box-shadow: inset 0 1px 1px rgba($swal2-black, .06); +$swal2-input-focus-border: 1px solid #b4dbed; +$swal2-input-focus-outline: none; +$swal2-input-focus-box-shadow: 0 0 3px #c4e6f5; +$swal2-input-font-size: 1.125em; +$swal2-input-background: inherit; +$swal2-input-color: inherit; +$swal2-input-transition: border-color .3s, box-shadow .3s; + +// TEXTAREA SPECIFIC VARIABLES +$swal2-textarea-height: 6.75em; +$swal2-textarea-padding: .75em; + +// VALIDATION MESSAGE +$swal2-validation-message-justify-content: center; +$swal2-validation-message-padding: .625em; +$swal2-validation-message-background: lighten($swal2-black, 94); +$swal2-validation-message-color: lighten($swal2-black, 40); +$swal2-validation-message-font-size: 1em; +$swal2-validation-message-font-weight: 300; +$swal2-validation-message-icon-background: $swal2-error; +$swal2-validation-message-icon-color: $swal2-white; +$swal2-validation-message-icon-zoom: null; + +// PROGRESS STEPS +$swal2-progress-steps-background: inherit; +$swal2-progress-steps-margin: 0 0 1.25em; +$swal2-progress-steps-padding: 0; +$swal2-progress-steps-font-weight: 600; +$swal2-progress-steps-distance: 2.5em; +$swal2-progress-step-width: 2em; +$swal2-progress-step-height: 2em; +$swal2-progress-step-border-radius: 2em; +$swal2-progress-step-background: #add8e6; +$swal2-progress-step-color: $swal2-white; +$swal2-active-step-background: #3085d6; +$swal2-active-step-color: $swal2-white; + +// FOOTER +$swal2-footer-margin: 1.25em 0 0; +$swal2-footer-padding: 1em 0 0; +$swal2-footer-border-color: #eee; +$swal2-footer-color: lighten($swal2-black, 33); +$swal2-footer-font-size: 1em; + +// TIMER POGRESS BAR +$swal2-timer-progress-bar-height: .25em; +$swal2-timer-progress-bar-background: rgba($swal2-black, .2); + +// CLOSE BUTTON +$swal2-close-button-width: 1.2em; +$swal2-close-button-height: 1.2em; +$swal2-close-button-line-height: 1.2; +$swal2-close-button-position: absolute; +$swal2-close-button-gap: 0; +$swal2-close-button-transition: color .1s ease-out; +$swal2-close-button-border: none; +$swal2-close-button-border-radius: 0; +$swal2-close-button-outline: initial; +$swal2-close-button-background: transparent; +$swal2-close-button-color: lighten($swal2-black, 80); +$swal2-close-button-font-family: serif; +$swal2-close-button-font-size: 2.5em; + +// CLOSE BUTTON:HOVER +$swal2-close-button-hover-transform: none; +$swal2-close-button-hover-color: $swal2-error; +$swal2-close-button-hover-background: transparent; + +// ACTIONS +$swal2-actions-flex-wrap: wrap; +$swal2-actions-align-items: center; +$swal2-actions-justify-content: center; +$swal2-actions-width: 100%; +$swal2-actions-margin: 1.25em auto 0; + +// CONFIRM BUTTON +$swal2-confirm-button-border: 0; +$swal2-confirm-button-border-radius: .25em; +$swal2-confirm-button-background-color: $background-tertiary; +$swal2-confirm-button-color: $swal2-white; +$swal2-confirm-button-font-size: 1.0625em; + +// CANCEL BUTTON +$swal2-cancel-button-border: 0; +$swal2-cancel-button-border-radius: .25em; +$swal2-cancel-button-background-color: #aaa; +$swal2-cancel-button-color: $swal2-white; +$swal2-cancel-button-font-size: 1.0625em; + +// COMMON VARIABLES FOR CONFIRM AND CANCEL BUTTONS +$swal2-button-darken-hover: rgba($swal2-black, .1); +$swal2-button-darken-active: rgba($swal2-black, .2); +$swal2-button-focus-outline: none; +$swal2-button-focus-background-color: null; +$swal2-button-focus-box-shadow: 0 0 0 1px $swal2-background, 0 0 0 3px $swal2-outline-color; + +// TOASTS +$swal2-toast-show-animation: swal2-toast-show .5s; +$swal2-toast-hide-animation: swal2-toast-hide .1s forwards; +$swal2-toast-border: none; +$swal2-toast-box-shadow: 0 0 .625em #d9d9d9; +$swal2-toast-background: $swal2-white; +$swal2-toast-close-button-width: .8em; +$swal2-toast-close-button-height: .8em; +$swal2-toast-close-button-line-height: .8; +$swal2-toast-width: auto; +$swal2-toast-padding: .625em; +$swal2-toast-title-margin: 0 .6em; +$swal2-toast-title-font-size: 1em; +$swal2-toast-content-font-size: 1em; +$swal2-toast-input-font-size: 1em; +$swal2-toast-validation-font-size: 1em; +$swal2-toast-buttons-font-size: 1em; +$swal2-toast-button-focus-box-shadow: 0 0 0 1px $swal2-background, 0 0 0 3px $swal2-outline-color; +$swal2-toast-footer-margin: .5em 0 0; +$swal2-toast-footer-padding: .5em 0 0; +$swal2-toast-footer-font-size: .8em; + +@import "~sweetalert2/src/sweetalert2.scss"; \ No newline at end of file diff --git a/client/src/assets/styles/vendor/_tooltip.scss b/client/src/assets/styles/vendor/_tooltip.scss new file mode 100644 index 00000000..baece684 --- /dev/null +++ b/client/src/assets/styles/vendor/_tooltip.scss @@ -0,0 +1,109 @@ +.tooltip { + display: block !important; + z-index: 10000; + + .tooltip-inner { + background: darken($background-floating, $amount: 5); + color: $text-normal; + border-radius: 3px; + padding: 5px 10px 4px; + } + + .tooltip-arrow { + width: 0; + height: 0; + border-style: solid; + position: absolute; + margin: 5px; + border-color: darken($background-floating, $amount: 5); + z-index: 1; + } + + &[x-placement^="top"] { + margin-bottom: 5px; + + .tooltip-arrow { + border-width: 5px 5px 0 5px; + border-left-color: transparent !important; + border-right-color: transparent !important; + border-bottom-color: transparent !important; + bottom: -5px; + left: calc(50% - 5px); + margin-top: 0; + margin-bottom: 0; + } + } + + &[x-placement^="bottom"] { + margin-top: 5px; + + .tooltip-arrow { + border-width: 0 5px 5px 5px; + border-left-color: transparent !important; + border-right-color: transparent !important; + border-top-color: transparent !important; + top: -5px; + left: calc(50% - 5px); + margin-top: 0; + margin-bottom: 0; + } + } + + &[x-placement^="right"] { + margin-left: 5px; + + .tooltip-arrow { + border-width: 5px 5px 5px 0; + border-left-color: transparent !important; + border-top-color: transparent !important; + border-bottom-color: transparent !important; + left: -5px; + top: calc(50% - 5px); + margin-left: 0; + margin-right: 0; + } + } + + &[x-placement^="left"] { + margin-right: 5px; + + .tooltip-arrow { + border-width: 5px 0 5px 5px; + border-top-color: transparent !important; + border-right-color: transparent !important; + border-bottom-color: transparent !important; + right: -5px; + top: calc(50% - 5px); + margin-left: 0; + margin-right: 0; + } + } + + &.popover { + $color: $text-normal; + + .popover-inner { + background: $color; + color: $background-floating; + padding: 24px; + border-radius: 3px; + box-shadow: 0 5px 30px rgba(black, .1); + } + + .popover-arrow { + border-color: $color; + } + } + + &[aria-hidden='true'] { + visibility: hidden; + opacity: 0; + transition: opacity .15s, visibility .15s; + } + + &[aria-hidden='false'] { + visibility: visible; + opacity: 1; + transition: opacity .15s; + } +} \ No newline at end of file diff --git a/client/src/client/base.ts b/client/src/client/base.ts index c104cdda..dfbf5cfd 100644 --- a/client/src/client/base.ts +++ b/client/src/client/base.ts @@ -25,7 +25,7 @@ export abstract class BaseClient extends EventEmitter { protected _ws?: WebSocket protected _peer?: RTCPeerConnection protected _channel?: RTCDataChannel - protected _timeout?: number + protected _timeout?: NodeJS.Timeout protected _username?: string protected _state: RTCIceConnectionState = 'disconnected' @@ -264,7 +264,11 @@ export abstract class BaseClient extends EventEmitter { this.onDisconnected(new Error('connection timeout')) } - private onDisconnected(reason?: Error) { + protected onDisconnected(reason?: Error) { + if (this._timeout) { + clearTimeout(this._timeout) + } + if (this.socketOpen) { try { this._ws!.close() @@ -283,14 +287,14 @@ export abstract class BaseClient extends EventEmitter { this[EVENT.DISCONNECTED](reason) } - [EVENT.MESSAGE](event: string, payload: any) { + protected [EVENT.MESSAGE](event: string, payload: any) { this.emit('warn', `unhandled websocket event '${event}':`, payload) } - abstract [EVENT.CONNECTING](): void - abstract [EVENT.CONNECTED](): void - abstract [EVENT.DISCONNECTED](reason?: Error): void - abstract [EVENT.TRACK](event: RTCTrackEvent): void - abstract [EVENT.DATA](data: any): void - abstract [EVENT.IDENTITY.PROVIDE](payload: IdentityPayload): void + protected abstract [EVENT.CONNECTING](): void + protected abstract [EVENT.CONNECTED](): void + protected abstract [EVENT.DISCONNECTED](reason?: Error): void + protected abstract [EVENT.TRACK](event: RTCTrackEvent): void + protected abstract [EVENT.DATA](data: any): void + protected abstract [EVENT.IDENTITY.PROVIDE](payload: IdentityPayload): void } diff --git a/client/src/client/events.ts b/client/src/client/events.ts index deafb54a..7d0bae19 100644 --- a/client/src/client/events.ts +++ b/client/src/client/events.ts @@ -8,7 +8,9 @@ export const EVENT = { DATA: 'DATA', // Websocket Events - DISCONNECT: 'disconnect', + SYSTEM: { + DISCONNECT: 'system/disconnect', + }, SIGNAL: { ANSWER: 'signal/answer', PROVIDE: 'signal/provide', @@ -36,20 +38,37 @@ export const EVENT = { BAN: 'admin/ban', KICK: 'admin/kick', LOCK: 'admin/lock', + UNLOCK: 'admin/unlock', MUTE: 'admin/mute', UNMUTE: 'admin/unmute', - FORCE: { - CONTROL: 'admin/force/control', - RELEASE: 'admin/force/release', - }, + CONTROL: 'admin/control', + RELEASE: 'admin/release', }, } as const export type Events = typeof EVENT -export type WebSocketEvents = SystemEvents | ControlEvents | IdentityEvents | MemberEvents | SignalEvents | ChatEvents -export type SystemEvents = typeof EVENT.DISCONNECT + +export type WebSocketEvents = + | SystemEvents + | ControlEvents + | IdentityEvents + | MemberEvents + | SignalEvents + | ChatEvents + | AdminEvents + +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 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 ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOJI +export type AdminEvents = + | typeof EVENT.ADMIN.BAN + | typeof EVENT.ADMIN.KICK + | typeof EVENT.ADMIN.LOCK + | typeof EVENT.ADMIN.UNLOCK + | typeof EVENT.ADMIN.MUTE + | typeof EVENT.ADMIN.UNMUTE + | typeof EVENT.ADMIN.CONTROL + | typeof EVENT.ADMIN.RELEASE diff --git a/client/src/client/index.ts b/client/src/client/index.ts index a66e3d2a..5068ff29 100644 --- a/client/src/client/index.ts +++ b/client/src/client/index.ts @@ -1,10 +1,22 @@ import Vue from 'vue' import EventEmitter from 'eventemitter3' import { BaseClient, BaseEvents } from './base' - +import { Member } from './types' import { EVENT } from './events' import { accessor } from '~/store' -import { IdentityPayload, MemberListPayload, MemberDisconnectPayload, MemberPayload, ControlPayload } from './messages' + +import { + DisconnectPayload, + IdentityPayload, + MemberListPayload, + MemberDisconnectPayload, + MemberPayload, + ControlPayload, + ChatPayload, + EmojiPayload, + AdminPayload, + AdminTargetPayload, +} from './messages' interface NekoEvents extends BaseEvents {} @@ -26,18 +38,21 @@ export class NekoClient extends BaseClient implements EventEmitter { super.connect(url, password, username) } + private get id() { + return this.$accessor.user.id + } + ///////////////////////////// // Internal Events ///////////////////////////// - [EVENT.CONNECTING]() { - this.$accessor.setConnnecting(true) + protected [EVENT.CONNECTING]() { + this.$accessor.setConnnecting() } - [EVENT.CONNECTED]() { + protected [EVENT.CONNECTED]() { this.$accessor.setConnected(true) - this.$accessor.setConnnecting(false) - this.$accessor.video.clearStream() - this.$accessor.remote.clearHost() + this.$accessor.setConnected(true) + this.$vue.$notify({ group: 'neko', type: 'success', @@ -47,11 +62,14 @@ export class NekoClient extends BaseClient implements EventEmitter { }) } - [EVENT.DISCONNECTED](reason?: Error) { + protected [EVENT.DISCONNECTED](reason?: Error) { this.$accessor.setConnected(false) - this.$accessor.setConnnecting(false) - this.$accessor.video.clearStream() + + this.$accessor.remote.clearHost() this.$accessor.user.clearMembers() + this.$accessor.video.clear() + this.$accessor.chat.clear() + this.$vue.$notify({ group: 'neko', type: 'error', @@ -62,61 +80,85 @@ export class NekoClient extends BaseClient implements EventEmitter { }) } - [EVENT.TRACK](event: RTCTrackEvent) { - if (event.track.kind === 'audio') { + protected [EVENT.TRACK](event: RTCTrackEvent) { + const { track, streams } = event + if (track.kind === 'audio') { return } - this.$accessor.video.addStream(event.streams[0]) + + this.$accessor.video.addTrack([track, streams[0]]) this.$accessor.video.setStream(0) } - [EVENT.DATA](data: any) {} + protected [EVENT.DATA](data: any) {} + + ///////////////////////////// + // System Events + ///////////////////////////// + protected [EVENT.SYSTEM.DISCONNECT]({ message }: DisconnectPayload) { + this.onDisconnected(new Error(message)) + this.$vue.$swal({ + title: 'Error!', + text: message, + icon: 'error', + confirmButtonText: 'ok', + }) + } ///////////////////////////// // Identity Events ///////////////////////////// - [EVENT.IDENTITY.PROVIDE]({ id }: IdentityPayload) { + protected [EVENT.IDENTITY.PROVIDE]({ id }: IdentityPayload) { this.$accessor.user.setMember(id) } ///////////////////////////// // Member Events ///////////////////////////// - [EVENT.MEMBER.LIST]({ members }: MemberListPayload) { + protected [EVENT.MEMBER.LIST]({ members }: MemberListPayload) { this.$accessor.user.setMembers(members) } - [EVENT.MEMBER.CONNECTED](member: MemberPayload) { + protected [EVENT.MEMBER.CONNECTED](member: MemberPayload) { this.$accessor.user.addMember(member) - if (member.id !== this.$accessor.user.id) { - this.$vue.$notify({ - group: 'neko', - type: 'info', - title: `${member.username} connected`, - duration: 5000, - speed: 1000, + if (member.id !== this.id) { + this.$accessor.chat.addMessage({ + id: member.id, + content: 'connected', + type: 'event', + created: new Date(), }) } } - [EVENT.MEMBER.DISCONNECTED]({ id }: MemberDisconnectPayload) { - this.$vue.$notify({ - group: 'neko', - type: 'info', - title: `${this.$accessor.user.members[id].username} disconnected`, - duration: 5000, - speed: 1000, + protected [EVENT.MEMBER.DISCONNECTED]({ id }: MemberDisconnectPayload) { + const member = this.member(id) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id: member.id, + content: 'disconnected', + type: 'event', + created: new Date(), }) + this.$accessor.user.delMember(id) } ///////////////////////////// // Control Events ///////////////////////////// - [EVENT.CONTROL.LOCKED]({ id }: ControlPayload) { + protected [EVENT.CONTROL.LOCKED]({ id }: ControlPayload) { this.$accessor.remote.setHost(id) - if (this.$accessor.user.id === id) { + const member = this.member(id) + if (!member) { + return + } + + if (this.id === id) { this.$vue.$notify({ group: 'neko', type: 'info', @@ -125,19 +167,23 @@ export class NekoClient extends BaseClient implements EventEmitter { speed: 1000, }) } else { - this.$vue.$notify({ - group: 'neko', - type: 'info', - title: `${this.$accessor.user.members[id].username} took the controls`, - duration: 5000, - speed: 1000, + this.$accessor.chat.addMessage({ + id: member.id, + content: 'took the controls', + type: 'event', + created: new Date(), }) } } - [EVENT.CONTROL.RELEASE]({ id }: ControlPayload) { + protected [EVENT.CONTROL.RELEASE]({ id }: ControlPayload) { this.$accessor.remote.clearHost() - if (this.$accessor.user.id === id) { + const member = this.member(id) + if (!member) { + return + } + + if (this.id === id) { this.$vue.$notify({ group: 'neko', type: 'info', @@ -146,34 +192,212 @@ export class NekoClient extends BaseClient implements EventEmitter { speed: 1000, }) } else { - this.$vue.$notify({ - group: 'neko', - type: 'info', - title: `The controls released from ${this.$accessor.user.members[id].username}`, - duration: 5000, - speed: 1000, + this.$accessor.chat.addMessage({ + id: member.id, + content: 'released the controls', + type: 'event', + created: new Date(), }) } } - [EVENT.CONTROL.REQUEST]({ id }: ControlPayload) { + protected [EVENT.CONTROL.REQUEST]({ id }: ControlPayload) { + const member = this.member(id) + if (!member) { + return + } + this.$vue.$notify({ group: 'neko', type: 'info', - title: `${this.$accessor.user.members[id].username} has the controls`, + title: `${member.username} has the controls`, text: 'But I let them know you wanted it', duration: 5000, speed: 1000, }) } - [EVENT.CONTROL.REQUESTING]({ id }: ControlPayload) { + protected [EVENT.CONTROL.REQUESTING]({ id }: ControlPayload) { + const member = this.member(id) + if (!member) { + return + } + this.$vue.$notify({ group: 'neko', type: 'info', - title: `${this.$accessor.user.members[id].username} is requesting the controls`, + title: `${member.username} is requesting the controls`, duration: 5000, speed: 1000, }) } + + ///////////////////////////// + // Chat Events + ///////////////////////////// + protected [EVENT.CHAT.MESSAGE]({ id, content }: ChatPayload) { + this.$accessor.chat.addMessage({ + id, + content, + type: 'text', + created: new Date(), + }) + } + + protected [EVENT.CHAT.EMOJI]({ id, emoji }: EmojiPayload) { + // + } + + ///////////////////////////// + // Admin Events + ///////////////////////////// + protected [EVENT.ADMIN.BAN]({ id, target }: AdminTargetPayload) { + if (!target) { + return + } + + const member = this.member(target) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id, + content: `banned ${member.username}`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.KICK]({ id, target }: AdminTargetPayload) { + if (!target) { + return + } + + const member = this.member(target) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id, + content: `kicked ${member.username}`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.MUTE]({ id, target }: AdminTargetPayload) { + if (!target) { + return + } + + this.$accessor.user.setMuted({ id: target, muted: true }) + + const member = this.member(target) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id, + content: `muted ${member.username}`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.UNMUTE]({ id, target }: AdminTargetPayload) { + if (!target) { + return + } + + this.$accessor.user.setMuted({ id: target, muted: false }) + + const member = this.member(target) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id, + content: `unmuted ${member.username}`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.LOCK]({ id }: AdminPayload) { + this.$accessor.chat.addMessage({ + id, + content: `locked the room`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.UNLOCK]({ id }: AdminPayload) { + this.$accessor.chat.addMessage({ + id, + content: `unlocked the room`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.CONTROL]({ id, target }: AdminTargetPayload) { + this.$accessor.remote.setHost(id) + + if (!target) { + this.$accessor.chat.addMessage({ + id, + content: `force took the controls`, + type: 'event', + created: new Date(), + }) + return + } + + const member = this.member(target) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id, + content: `took the controls from ${member.username}`, + type: 'event', + created: new Date(), + }) + } + + protected [EVENT.ADMIN.RELEASE]({ id, target }: AdminTargetPayload) { + this.$accessor.remote.clearHost() + if (!target) { + this.$accessor.chat.addMessage({ + id, + content: `force released the controls`, + type: 'event', + created: new Date(), + }) + return + } + + const member = this.member(target) + if (!member) { + return + } + + this.$accessor.chat.addMessage({ + id, + content: `released the controls from ${member.username}`, + type: 'event', + created: new Date(), + }) + } + + // Utilities + protected member(id: string): Member | undefined { + return this.$accessor.user.members[id] + } } diff --git a/client/src/client/messages.ts b/client/src/client/messages.ts index 73b4720c..400f9c2a 100644 --- a/client/src/client/messages.ts +++ b/client/src/client/messages.ts @@ -18,11 +18,25 @@ export type WebSocketPayloads = | Member | ControlPayload | ChatPayload + | ChatSendPayload + | EmojiSendPayload + | AdminPayload export interface WebSocketMessage { event: WebSocketEvents | string } +/* + SYSTEM MESSAGES/PAYLOADS +*/ +// system/disconnect +export interface DisconnectMessage extends WebSocketMessage, DisconnectPayload { + event: typeof EVENT.SYSTEM.DISCONNECT +} +export interface DisconnectPayload { + message: string +} + /* IDENTITY MESSAGES/PAYLOADS */ @@ -84,12 +98,49 @@ export interface ControlPayload { /* CHAT PAYLOADS */ -// chat/send & chat/receive +// chat/message export interface ChatMessage extends WebSocketMessage, ChatPayload { - event: typeof EVENT.CHAT.SEND | typeof EVENT.CHAT.RECEIVE + event: typeof EVENT.CHAT.MESSAGE } +export interface ChatSendPayload { + content: string +} export interface ChatPayload { id: string content: string } + +// chat/emoji +export interface ChatEmojiMessage extends WebSocketMessage, EmojiPayload { + event: typeof EVENT.CHAT.EMOJI +} + +export interface EmojiPayload { + id: string + emoji: string +} + +export interface EmojiSendPayload { + emoji: string +} + +/* + ADMIN PAYLOADS +*/ +export interface AdminMessage extends WebSocketMessage, AdminPayload { + event: typeof EVENT.MESSAGE +} + +export interface AdminPayload { + id: string +} + +export interface AdminTargetMessage extends WebSocketMessage, AdminTargetPayload { + event: typeof EVENT.CHAT.EMOJI +} + +export interface AdminTargetPayload { + id: string + target?: string +} diff --git a/client/src/client/types.ts b/client/src/client/types.ts index db27a759..ed4254b3 100644 --- a/client/src/client/types.ts +++ b/client/src/client/types.ts @@ -2,4 +2,6 @@ export interface Member { id: string username: string admin: boolean + muted: boolean + connected?: boolean } diff --git a/client/src/components/about.vue b/client/src/components/about.vue new file mode 100644 index 00000000..de89b6a7 --- /dev/null +++ b/client/src/components/about.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/client/src/components/chat.vue b/client/src/components/chat.vue new file mode 100644 index 00000000..5f88e921 --- /dev/null +++ b/client/src/components/chat.vue @@ -0,0 +1,416 @@ +