Merge branch 'dev' into more-layout-changes
5
.env
@ -1,2 +1,7 @@
|
||||
BROWSER=none
|
||||
GENERATE_SOURCEMAP=false
|
||||
REACT_APP_CONFIG_URLS=/renderer-config.json,/ui-config.json
|
||||
REACT_APP_SOCKET_URL=wss://ws.nitrots.co:2096
|
||||
REACT_APP_ASSET_URL=https://assets.nitrodev.co
|
||||
REACT_APP_IMAGE_LIBRARY_URL=https://swf.nitrots.co/c_images/
|
||||
REACT_APP_HOF_FURNI_URL=https://swf.nitrots.co/dcr/hof_furni
|
||||
|
7
.env.default
Normal file
@ -0,0 +1,7 @@
|
||||
BROWSER=none
|
||||
GENERATE_SOURCEMAP=false
|
||||
REACT_APP_CONFIG_URLS=/renderer-config.json,/ui-config.json
|
||||
REACT_APP_SOCKET_URL=wss://ws.server.com:2096
|
||||
REACT_APP_ASSET_URL=https://nitro.server.com
|
||||
REACT_APP_IMAGE_LIBRARY_URL=https://swf.server.com/c_images/
|
||||
REACT_APP_HOF_FURNI_URL=https://swf.server.com/dcr/hof_furni
|
34
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: dedicated-server
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "yarn"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn remove @nitrots/nitro-renderer
|
||||
yarn add git+https://git@git.krews.org/nitro/nitro-renderer#dev
|
||||
yarn install
|
||||
- name: Build Nitro
|
||||
run: |
|
||||
yarn build
|
||||
- name: Archive Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: |
|
||||
build
|
49
.github/workflows/deploy.yml
vendored
@ -1,49 +0,0 @@
|
||||
name: Deploy Bundle@dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '~/.npm'
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm uninstall @nitrots/nitro-renderer
|
||||
npm install git+https://git@git.krews.org/nitro/nitro-renderer#dev
|
||||
- name: Build Nitro
|
||||
run: |
|
||||
npm run build
|
||||
- name: Archive Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: |
|
||||
build
|
||||
- name: Deploy Artifacts
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
REMOTE_HOST: ${{ secrets.HOST }}
|
||||
REMOTE_PORT: ${{ secrets.PORT }}
|
||||
REMOTE_USER: ${{ secrets.USERNAME }}
|
||||
SSH_PRIVATE_KEY: ${{ secrets.SSHKEY }}
|
||||
SOURCE: "build/"
|
||||
TARGET: ${{ secrets.TARGET }}
|
19560
package-lock.json
generated
12
package.json
@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"build": "craco --max_old_space_size=8048 build",
|
||||
"build:prod": "npm uninstall @nitrots/nitro-renderer && npm i git+https://git@git.krews.org/nitro/nitro-renderer#dev && npm i && npm run build",
|
||||
"build:prod": "npx browserslist@latest --update-db && yarn remove @nitrots/nitro-renderer && yarn add git+https://git@git.krews.org/nitro/nitro-renderer#dev && yarn install && yarn build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
@ -17,7 +17,7 @@
|
||||
"@nitrots/nitro-renderer": "file:../nitro-renderer",
|
||||
"animate.css": "^4.1.1",
|
||||
"classnames": "^2.3.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-bootstrap": "^2.0.0-alpha.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@ -26,15 +26,9 @@
|
||||
"react-transition-group": "^4.4.2",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"react-youtube": "^7.13.1",
|
||||
"styled-components": "^5.3.3",
|
||||
"typescript": "^4.3.5",
|
||||
"web-vitals": "^1.1.2"
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^12.20.19",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
|
34
post-install.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { request as httpsRequest } from 'https';
|
||||
|
||||
function install()
|
||||
{
|
||||
try
|
||||
{
|
||||
const params = {};
|
||||
|
||||
params['packageName'] = process.env.npm_package_name;
|
||||
params['packageVersion'] = process.env.npm_package_version;
|
||||
|
||||
const data = JSON.stringify(params);
|
||||
const request = httpsRequest({
|
||||
hostname: 'install.nitrots.co',
|
||||
port: 443,
|
||||
path: '/collect',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': data.length
|
||||
}
|
||||
});
|
||||
|
||||
request.write(data);
|
||||
request.end();
|
||||
}
|
||||
|
||||
catch (e)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
install();
|
@ -13,9 +13,13 @@
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root" class="w-100 h-100"></div>
|
||||
<script>
|
||||
var NitroConfig = {
|
||||
configurationUrls: [ '/renderer-config.json', '/ui-config.json' ],
|
||||
sso: (new URLSearchParams(window.location.search).get('sso') || null)
|
||||
const NitroConfig = {
|
||||
"config.urls": ("%REACT_APP_CONFIG_URLS%").split(','),
|
||||
"sso.ticket": (new URLSearchParams(window.location.search).get('sso') || null),
|
||||
"socket.url": "%REACT_APP_SOCKET_URL%",
|
||||
"asset.url": "%REACT_APP_ASSET_URL%",
|
||||
"image.library.url": "%REACT_APP_IMAGE_LIBRARY_URL%",
|
||||
"hof.furni.url": "%REACT_APP_HOF_FURNI_URL%"
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"socket.url": "",
|
||||
"asset.url": "",
|
||||
"image.library.url": "C_IMAGES_URL",
|
||||
"hof.furni.url": "HOF_FURNI_URL",
|
||||
"image.library.url": "",
|
||||
"hof.furni.url": "",
|
||||
"images.url": "${asset.url}/images",
|
||||
"gamedata.url": "${asset.url}/gamedata",
|
||||
"sounds.url": "${asset.url}/sounds/%sample%.mp3",
|
||||
@ -21,9 +21,7 @@
|
||||
"pet.asset.url": "${asset.url}/bundled/pet/%libname%.nitro",
|
||||
"generic.asset.url": "${asset.url}/bundled/generic/%libname%.nitro",
|
||||
"badge.asset.url": "${image.library.url}album1584/%badgename%.gif",
|
||||
"badge.asset.group.url": "GROUP_BADGE_URL/%badgedata%.gif",
|
||||
"badge.asset.group.external.url": "",
|
||||
"badge.asset.grouparts.url": "GROUP_BADGE_PART_URL/badgepart_%part%.png",
|
||||
"badge.asset.grouparts.url": "${image.library.url}Badgeparts/badgepart_%part%.png",
|
||||
"furni.rotation.bounce.steps": 20,
|
||||
"furni.rotation.bounce.height": 0.0625,
|
||||
"enable.avatar.arrow": false,
|
||||
@ -101,20 +99,8 @@
|
||||
"elephants"
|
||||
],
|
||||
"preload.assets.urls": [
|
||||
"${images.url}/additions/user_blowkiss.png",
|
||||
"${images.url}/additions/user_idle_left_1.png",
|
||||
"${images.url}/additions/user_idle_left_2.png",
|
||||
"${images.url}/additions/user_idle_right_1.png",
|
||||
"${images.url}/additions/user_idle_right_2.png",
|
||||
"${images.url}/additions/user_muted.png",
|
||||
"${images.url}/additions/user_muted_small.png",
|
||||
"${images.url}/additions/user_typing.png",
|
||||
"${images.url}/additions/number_1.png",
|
||||
"${images.url}/additions/number_2.png",
|
||||
"${images.url}/additions/number_3.png",
|
||||
"${images.url}/additions/number_4.png",
|
||||
"${images.url}/additions/number_5.png",
|
||||
"${images.url}/additions/pet_experience_bubble.png",
|
||||
"${asset.url}/bundled/generic/avatar_additions.nitro",
|
||||
"${asset.url}/bundled/generic/floor_editor.nitro",
|
||||
"${images.url}/loading_icon.png",
|
||||
"${images.url}/clear_icon.png",
|
||||
"${images.url}/big_arrow.png"
|
||||
|
@ -47,6 +47,15 @@
|
||||
5,
|
||||
101
|
||||
],
|
||||
"hc.center": {
|
||||
"benefits.info": true,
|
||||
"payday.info": true,
|
||||
"gift.info": true,
|
||||
"benefits.habbopage": "habboclub",
|
||||
"payday.habbopage": "hcpayday",
|
||||
"catalog.buy": "habbo_club",
|
||||
"catalog.gifts": "club_gifts"
|
||||
},
|
||||
"currency.display.number.short": false,
|
||||
"currency.asset.icon.url": "${images.url}/wallet/%type%.png",
|
||||
"catalog.asset.url": "${image.library.url}catalogue",
|
||||
|
@ -56,6 +56,9 @@ $help-height: 450px;
|
||||
$nitropedia-width: 400px;
|
||||
$nitropedia-height: 400px;
|
||||
|
||||
$messenger-width: 500px;
|
||||
$messenger-height: 370px;
|
||||
|
||||
.nitro-app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
BIN
src/assets/images/hc-center/benefits.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/assets/images/hc-center/clock.png
Normal file
After Width: | Height: | Size: 267 B |
BIN
src/assets/images/hc-center/hc_logo.gif
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/hc-center/payday.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
src/assets/images/icons/chat-history.png
Normal file
After Width: | Height: | Size: 574 B |
BIN
src/assets/images/icons/cog.png
Normal file
After Width: | Height: | Size: 448 B |
BIN
src/assets/images/icons/like-room.png
Normal file
After Width: | Height: | Size: 481 B |
BIN
src/assets/images/icons/room-link.png
Normal file
After Width: | Height: | Size: 322 B |
BIN
src/assets/images/icons/small-room.png
Normal file
After Width: | Height: | Size: 413 B |
BIN
src/assets/images/icons/zoom-less.png
Normal file
After Width: | Height: | Size: 252 B |
BIN
src/assets/images/icons/zoom-more.png
Normal file
After Width: | Height: | Size: 475 B |
@ -4,34 +4,37 @@
|
||||
// `<nav>`s, `<ul>`s or `<ol>`s.
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: block;
|
||||
padding: $nav-link-padding-y $nav-link-padding-x;
|
||||
@include font-size($nav-link-font-size);
|
||||
font-weight: $nav-link-font-weight;
|
||||
color: $nav-link-color;
|
||||
text-decoration: if($link-decoration == none, null, none);
|
||||
@include transition($nav-link-transition);
|
||||
display: block;
|
||||
padding: $nav-link-padding-y $nav-link-padding-x;
|
||||
@include font-size($nav-link-font-size);
|
||||
font-weight: $nav-link-font-weight;
|
||||
color: $nav-link-color;
|
||||
text-decoration: if($link-decoration == none, null, none);
|
||||
@include transition($nav-link-transition);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $nav-link-hover-color;
|
||||
text-decoration: if($link-hover-decoration == underline, none, null);
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// Disabled state lightens text
|
||||
&.disabled {
|
||||
color: $nav-link-disabled-color;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $nav-link-hover-color;
|
||||
text-decoration: if($link-hover-decoration == underline, none, null);
|
||||
}
|
||||
|
||||
// Disabled state lightens text
|
||||
&.disabled {
|
||||
color: $nav-link-disabled-color;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -39,122 +42,119 @@
|
||||
//
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;
|
||||
border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;
|
||||
|
||||
.nav-link {
|
||||
position: relative;
|
||||
margin-bottom: -$nav-tabs-border-width;
|
||||
background-color: $nav-tabs-link-bg;
|
||||
border: $nav-tabs-border-width solid $nav-tabs-border-color;
|
||||
@include border-top-radius($nav-tabs-border-radius);
|
||||
.nav-link {
|
||||
position: relative;
|
||||
margin-bottom: -$nav-tabs-border-width;
|
||||
background-color: $nav-tabs-link-bg;
|
||||
border: $nav-tabs-border-width solid $nav-tabs-border-color;
|
||||
@include border-top-radius($nav-tabs-border-radius);
|
||||
|
||||
&.active {
|
||||
&:before {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
&.active {
|
||||
&:before {
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 93%;
|
||||
height: 3px;
|
||||
border-radius: $border-radius;
|
||||
top: 1.5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
background: #c2c9d1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: $nav-tabs-link-hover-border-color;
|
||||
// Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $nav-link-disabled-color;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 93%;
|
||||
height: 3px;
|
||||
border-radius: $border-radius;
|
||||
top: 1.5px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
background: #C2C9D1;
|
||||
z-index: 1;
|
||||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: $nav-tabs-link-active-color;
|
||||
background-color: $nav-tabs-link-active-bg;
|
||||
border-color: $nav-tabs-link-active-border-color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: $nav-tabs-link-hover-border-color;
|
||||
// Prevents active .nav-link tab overlapping focus outline of previous/next .nav-link
|
||||
isolation: isolate;
|
||||
.dropdown-menu {
|
||||
// Make dropdown border overlap tab border
|
||||
margin-top: -$nav-tabs-border-width;
|
||||
// Remove the top rounded corners here since there is a hard edge above the menu
|
||||
@include border-top-radius(0);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $nav-link-disabled-color;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: $nav-tabs-link-active-color;
|
||||
background-color: $nav-tabs-link-active-bg;
|
||||
border-color: $nav-tabs-link-active-border-color;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
// Make dropdown border overlap tab border
|
||||
margin-top: -$nav-tabs-border-width;
|
||||
// Remove the top rounded corners here since there is a hard edge above the menu
|
||||
@include border-top-radius(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Pills
|
||||
//
|
||||
|
||||
.nav-pills {
|
||||
.nav-link {
|
||||
background: none;
|
||||
border: 0;
|
||||
@include border-radius($nav-pills-border-radius);
|
||||
}
|
||||
.nav-link {
|
||||
background: none;
|
||||
border: 0;
|
||||
@include border-radius($nav-pills-border-radius);
|
||||
}
|
||||
|
||||
.nav-link.active,
|
||||
.show > .nav-link {
|
||||
color: $nav-pills-link-active-color;
|
||||
@include gradient-bg($nav-pills-link-active-bg);
|
||||
}
|
||||
.nav-link.active,
|
||||
.show > .nav-link {
|
||||
color: $nav-pills-link-active-color;
|
||||
@include gradient-bg($nav-pills-link-active-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Justified variants
|
||||
//
|
||||
|
||||
.nav-fill {
|
||||
> .nav-link,
|
||||
.nav-item {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
}
|
||||
> .nav-link,
|
||||
.nav-item {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-justified {
|
||||
> .nav-link,
|
||||
.nav-item {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
> .nav-link,
|
||||
.nav-item {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-fill,
|
||||
.nav-justified {
|
||||
.nav-item .nav-link {
|
||||
width: 100%; // Make sure button will grow
|
||||
}
|
||||
.nav-item .nav-link {
|
||||
width: 100%; // Make sure button will grow
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Tabbable tabs
|
||||
//
|
||||
// Hide tabbable panes to start, show them when `.active`
|
||||
|
||||
.tab-content {
|
||||
> .tab-pane {
|
||||
display: none;
|
||||
}
|
||||
> .active {
|
||||
display: block;
|
||||
}
|
||||
> .tab-pane {
|
||||
display: none;
|
||||
}
|
||||
> .active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
|
||||
|
||||
|
||||
// Reboot
|
||||
//
|
||||
// Normalization of HTML elements, manually forked from Normalize.css to remove
|
||||
@ -8,7 +7,6 @@
|
||||
//
|
||||
// Normalize is licensed MIT. https://github.com/necolas/normalize.css
|
||||
|
||||
|
||||
// Document
|
||||
//
|
||||
// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.
|
||||
@ -16,27 +14,26 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
line-height: normal;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
// Root
|
||||
//
|
||||
// Ability to the value of the root font sizes, affecting the value of `rem`.
|
||||
// null by default, thus nothing is generated.
|
||||
|
||||
:root {
|
||||
@if $font-size-root != null {
|
||||
font-size: var(--#{$variable-prefix}-root-font-size);
|
||||
}
|
||||
|
||||
@if $enable-smooth-scroll {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
scroll-behavior: smooth;
|
||||
@if $font-size-root != null {
|
||||
font-size: var(--#{$variable-prefix}-root-font-size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@if $enable-smooth-scroll {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
//
|
||||
@ -47,38 +44,36 @@
|
||||
|
||||
// scss-docs-start reboot-body-rules
|
||||
body {
|
||||
margin: 0; // 1
|
||||
font-family: var(--#{$variable-prefix}body-font-family);
|
||||
@include font-size(var(--#{$variable-prefix}body-font-size));
|
||||
font-weight: var(--#{$variable-prefix}body-font-weight);
|
||||
line-height: var(--#{$variable-prefix}body-line-height);
|
||||
color: var(--#{$variable-prefix}body-color);
|
||||
text-align: var(--#{$variable-prefix}body-text-align);
|
||||
background-color: var(--#{$variable-prefix}body-bg); // 2
|
||||
-webkit-text-size-adjust: 100%; // 3
|
||||
-webkit-tap-highlight-color: rgba($black, 0); // 4
|
||||
margin: 0; // 1
|
||||
font-family: var(--#{$variable-prefix}body-font-family);
|
||||
@include font-size(var(--#{$variable-prefix}body-font-size));
|
||||
font-weight: var(--#{$variable-prefix}body-font-weight);
|
||||
line-height: var(--#{$variable-prefix}body-line-height);
|
||||
color: var(--#{$variable-prefix}body-color);
|
||||
text-align: var(--#{$variable-prefix}body-text-align);
|
||||
background-color: var(--#{$variable-prefix}body-bg); // 2
|
||||
-webkit-text-size-adjust: 100%; // 3
|
||||
-webkit-tap-highlight-color: rgba($black, 0); // 4
|
||||
}
|
||||
// scss-docs-end reboot-body-rules
|
||||
|
||||
|
||||
// Content grouping
|
||||
//
|
||||
// 1. Reset Firefox's gray color
|
||||
// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field
|
||||
|
||||
hr {
|
||||
margin: $hr-margin-y 0;
|
||||
color: $hr-color; // 1
|
||||
background-color: currentColor;
|
||||
border: 0;
|
||||
opacity: $hr-opacity;
|
||||
margin: $hr-margin-y 0;
|
||||
color: $hr-color; // 1
|
||||
background-color: currentColor;
|
||||
border: 0;
|
||||
opacity: $hr-opacity;
|
||||
}
|
||||
|
||||
hr:not([size]) {
|
||||
height: $hr-height; // 2
|
||||
height: $hr-height; // 2
|
||||
}
|
||||
|
||||
|
||||
// Typography
|
||||
//
|
||||
// 1. Remove top margins from headings
|
||||
@ -86,57 +81,55 @@ hr:not([size]) {
|
||||
// margin for easier control within type scales as it avoids margin collapsing.
|
||||
|
||||
%heading {
|
||||
margin-top: 0; // 1
|
||||
margin-bottom: $headings-margin-bottom;
|
||||
font-family: $headings-font-family;
|
||||
font-style: $headings-font-style;
|
||||
font-weight: $headings-font-weight;
|
||||
line-height: $headings-line-height;
|
||||
color: $headings-color;
|
||||
margin-top: 0; // 1
|
||||
margin-bottom: $headings-margin-bottom;
|
||||
font-family: $headings-font-family;
|
||||
font-style: $headings-font-style;
|
||||
font-weight: $headings-font-weight;
|
||||
line-height: $headings-line-height;
|
||||
color: $headings-color;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend %heading;
|
||||
@include font-size($h1-font-size);
|
||||
@extend %heading;
|
||||
@include font-size($h1-font-size);
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend %heading;
|
||||
@include font-size($h2-font-size);
|
||||
@extend %heading;
|
||||
@include font-size($h2-font-size);
|
||||
}
|
||||
|
||||
h3 {
|
||||
@extend %heading;
|
||||
@include font-size($h3-font-size);
|
||||
@extend %heading;
|
||||
@include font-size($h3-font-size);
|
||||
}
|
||||
|
||||
h4 {
|
||||
@extend %heading;
|
||||
@include font-size($h4-font-size);
|
||||
@extend %heading;
|
||||
@include font-size($h4-font-size);
|
||||
}
|
||||
|
||||
h5 {
|
||||
@extend %heading;
|
||||
@include font-size($h5-font-size);
|
||||
@extend %heading;
|
||||
@include font-size($h5-font-size);
|
||||
}
|
||||
|
||||
h6 {
|
||||
@extend %heading;
|
||||
@include font-size($h6-font-size);
|
||||
@extend %heading;
|
||||
@include font-size($h6-font-size);
|
||||
}
|
||||
|
||||
|
||||
// Reset margins on paragraphs
|
||||
//
|
||||
// Similarly, the top margin on `<p>`s get reset. However, we also reset the
|
||||
// bottom margin to use `rem` units instead of `em`.
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: $paragraph-margin-bottom;
|
||||
margin-top: 0;
|
||||
margin-bottom: $paragraph-margin-bottom;
|
||||
}
|
||||
|
||||
|
||||
// Abbreviations
|
||||
//
|
||||
// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin
|
||||
@ -145,89 +138,83 @@ p {
|
||||
// 4. Prevent the text-decoration to be skipped.
|
||||
|
||||
abbr[title],
|
||||
abbr[data-bs-original-title] { // 1
|
||||
text-decoration: underline dotted; // 2
|
||||
cursor: help; // 3
|
||||
text-decoration-skip-ink: none; // 4
|
||||
abbr[data-bs-original-title] {
|
||||
// 1
|
||||
text-decoration: underline dotted; // 2
|
||||
cursor: help; // 3
|
||||
text-decoration-skip-ink: none; // 4
|
||||
}
|
||||
|
||||
|
||||
// Address
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
|
||||
// Lists
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: $dt-font-weight;
|
||||
font-weight: $dt-font-weight;
|
||||
}
|
||||
|
||||
// 1. Undo browser default
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0; // 1
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0; // 1
|
||||
}
|
||||
|
||||
|
||||
// Blockquote
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
|
||||
// Strong
|
||||
//
|
||||
// Add the correct font weight in Chrome, Edge, and Safari
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: $font-weight-bolder;
|
||||
font-weight: $font-weight-bolder;
|
||||
}
|
||||
|
||||
|
||||
// Small
|
||||
//
|
||||
// Add the correct font size in all browsers
|
||||
|
||||
small {
|
||||
@include font-size($small-font-size);
|
||||
@include font-size($small-font-size);
|
||||
}
|
||||
|
||||
|
||||
// Mark
|
||||
|
||||
mark {
|
||||
padding: $mark-padding;
|
||||
background-color: $mark-bg;
|
||||
padding: $mark-padding;
|
||||
background-color: $mark-bg;
|
||||
}
|
||||
|
||||
|
||||
// Sub and Sup
|
||||
//
|
||||
// Prevent `sub` and `sup` elements from affecting the line height in
|
||||
@ -235,26 +222,29 @@ mark {
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
@include font-size($sub-sup-font-size);
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
position: relative;
|
||||
@include font-size($sub-sup-font-size);
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub { bottom: -.25em; }
|
||||
sup { top: -.5em; }
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
// Links
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
text-decoration: $link-decoration;
|
||||
color: $link-color;
|
||||
text-decoration: $link-decoration;
|
||||
|
||||
&:hover {
|
||||
color: $link-hover-color;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
&:hover {
|
||||
color: $link-hover-color;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
}
|
||||
|
||||
// And undo these styles for placeholder links/named anchors (without href).
|
||||
@ -263,24 +253,25 @@ a {
|
||||
// See https://github.com/twbs/bootstrap/issues/19402
|
||||
|
||||
a:not([href]):not([class]) {
|
||||
&,
|
||||
&:hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
&,
|
||||
&:hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Code
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: $font-family-code;
|
||||
@include font-size(1em); // Correct the odd `em` font sizing in all browsers.
|
||||
direction: ltr #{"/* rtl:ignore */"};
|
||||
unicode-bidi: bidi-override;
|
||||
font-family: $font-family-code;
|
||||
@include font-size(
|
||||
1em
|
||||
); // Correct the odd `em` font sizing in all browsers.
|
||||
direction: ltr #{"/* rtl:ignore */"};
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
// 1. Remove browser default top margin
|
||||
@ -288,78 +279,75 @@ samp {
|
||||
// 3. Don't allow content to break outside
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0; // 1
|
||||
margin-bottom: 1rem; // 2
|
||||
overflow: auto; // 3
|
||||
@include font-size($code-font-size);
|
||||
color: $pre-color;
|
||||
display: block;
|
||||
margin-top: 0; // 1
|
||||
margin-bottom: 1rem; // 2
|
||||
overflow: auto; // 3
|
||||
@include font-size($code-font-size);
|
||||
color: $pre-color;
|
||||
|
||||
// Account for some code outputs that place code tags in pre tags
|
||||
code {
|
||||
@include font-size(inherit);
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
// Account for some code outputs that place code tags in pre tags
|
||||
code {
|
||||
@include font-size(inherit);
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
@include font-size($code-font-size);
|
||||
color: $code-color;
|
||||
word-wrap: break-word;
|
||||
@include font-size($code-font-size);
|
||||
color: $code-color;
|
||||
word-wrap: break-word;
|
||||
|
||||
// Streamline the style when inside anchors to avoid broken underline and more
|
||||
a > & {
|
||||
color: inherit;
|
||||
}
|
||||
// Streamline the style when inside anchors to avoid broken underline and more
|
||||
a > & {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: $kbd-padding-y $kbd-padding-x;
|
||||
@include font-size($kbd-font-size);
|
||||
color: $kbd-color;
|
||||
background-color: $kbd-bg;
|
||||
@include border-radius($border-radius-sm);
|
||||
padding: $kbd-padding-y $kbd-padding-x;
|
||||
@include font-size($kbd-font-size);
|
||||
color: $kbd-color;
|
||||
background-color: $kbd-bg;
|
||||
@include border-radius($border-radius-sm);
|
||||
|
||||
kbd {
|
||||
padding: 0;
|
||||
@include font-size(1em);
|
||||
font-weight: $nested-kbd-font-weight;
|
||||
}
|
||||
kbd {
|
||||
padding: 0;
|
||||
@include font-size(1em);
|
||||
font-weight: $nested-kbd-font-weight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Figures
|
||||
//
|
||||
// Apply a consistent margin strategy (matches our type styles).
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
|
||||
// Images and content
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
// Tables
|
||||
//
|
||||
// Prevent double borders
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: $table-cell-padding-y;
|
||||
padding-bottom: $table-cell-padding-y;
|
||||
color: $table-caption-color;
|
||||
text-align: left;
|
||||
padding-top: $table-cell-padding-y;
|
||||
padding-bottom: $table-cell-padding-y;
|
||||
color: $table-caption-color;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
// 1. Removes font-weight bold by inheriting
|
||||
@ -367,9 +355,9 @@ caption {
|
||||
// 3. Fix alignment for Safari
|
||||
|
||||
th {
|
||||
font-weight: $table-th-font-weight; // 1
|
||||
text-align: inherit; // 2
|
||||
text-align: -webkit-match-parent; // 3
|
||||
font-weight: $table-th-font-weight; // 1
|
||||
text-align: inherit; // 2
|
||||
text-align: -webkit-match-parent; // 3
|
||||
}
|
||||
|
||||
thead,
|
||||
@ -378,26 +366,25 @@ tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
|
||||
// Forms
|
||||
//
|
||||
// 1. Allow labels to use `margin` for spacing.
|
||||
|
||||
label {
|
||||
display: inline-block; // 1
|
||||
display: inline-block; // 1
|
||||
}
|
||||
|
||||
// Remove the default `border-radius` that macOS Chrome adds.
|
||||
// See https://github.com/twbs/bootstrap/issues/24093
|
||||
|
||||
button {
|
||||
// stylelint-disable-next-line property-disallowed-list
|
||||
border-radius: 0;
|
||||
// stylelint-disable-next-line property-disallowed-list
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
// Explicitly remove focus outline in Chromium when it shouldn't be
|
||||
@ -406,7 +393,7 @@ button {
|
||||
// confused and applies its very visible two-tone outline anyway.
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
// 1. Remove the margin in Firefox and Safari
|
||||
@ -416,40 +403,40 @@ button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0; // 1
|
||||
font-family: inherit;
|
||||
@include font-size(inherit);
|
||||
line-height: inherit;
|
||||
margin: 0; // 1
|
||||
font-family: inherit;
|
||||
@include font-size(inherit);
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
// Remove the inheritance of text transform in Firefox
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
text-transform: none;
|
||||
}
|
||||
// Set the cursor for non-`<button>` buttons
|
||||
//
|
||||
// Details at https://github.com/twbs/bootstrap/pull/30562
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
// Remove the inheritance of word-wrap in Safari.
|
||||
// See https://github.com/twbs/bootstrap/issues/24990
|
||||
word-wrap: normal;
|
||||
// Remove the inheritance of word-wrap in Safari.
|
||||
// See https://github.com/twbs/bootstrap/issues/24990
|
||||
word-wrap: normal;
|
||||
|
||||
// Undo the opacity change from Chrome
|
||||
&:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
// Undo the opacity change from Chrome
|
||||
&:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the dropdown arrow in Chrome from inputs built with datalists.
|
||||
// See https://stackoverflow.com/a/54997118
|
||||
|
||||
[list]::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||
@ -461,26 +448,26 @@ button,
|
||||
[type="button"], // 1
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button; // 2
|
||||
-webkit-appearance: button; // 2
|
||||
|
||||
@if $enable-button-pointers {
|
||||
&:not(:disabled) {
|
||||
cursor: pointer; // 3
|
||||
@if $enable-button-pointers {
|
||||
&:not(:disabled) {
|
||||
cursor: pointer; // 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
// 1. Textareas should really only resize vertically so they don't break their (horizontal) containers.
|
||||
|
||||
textarea {
|
||||
resize: vertical; // 1
|
||||
resize: vertical; // 1
|
||||
}
|
||||
|
||||
// 1. Browsers set a default `min-width: min-content;` on fieldsets,
|
||||
@ -491,10 +478,10 @@ textarea {
|
||||
// 2. Reset the default outline behavior of fieldsets so they don't affect page layout.
|
||||
|
||||
fieldset {
|
||||
min-width: 0; // 1
|
||||
padding: 0; // 2
|
||||
margin: 0; // 2
|
||||
border: 0; // 2
|
||||
min-width: 0; // 1
|
||||
padding: 0; // 2
|
||||
margin: 0; // 2
|
||||
border: 0; // 2
|
||||
}
|
||||
|
||||
// 1. By using `float: left`, the legend will behave like a block element.
|
||||
@ -503,17 +490,17 @@ fieldset {
|
||||
// See https://github.com/twbs/bootstrap/issues/29712
|
||||
|
||||
legend {
|
||||
float: left; // 1
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: $legend-margin-bottom;
|
||||
@include font-size($legend-font-size);
|
||||
font-weight: $legend-font-weight;
|
||||
line-height: inherit;
|
||||
float: left; // 1
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: $legend-margin-bottom;
|
||||
@include font-size($legend-font-size);
|
||||
font-weight: $legend-font-weight;
|
||||
line-height: inherit;
|
||||
|
||||
+ * {
|
||||
clear: left; // 2
|
||||
}
|
||||
+ * {
|
||||
clear: left; // 2
|
||||
}
|
||||
}
|
||||
|
||||
// Fix height of inputs with a type of datetime-local, date, month, week, or time
|
||||
@ -526,11 +513,11 @@ legend {
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// 1. Correct the outline style in Safari.
|
||||
@ -540,8 +527,8 @@ legend {
|
||||
// https://github.com/twbs/bootstrap/issues/11586.
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px; // 1
|
||||
-webkit-appearance: textfield; // 2
|
||||
outline-offset: -2px; // 1
|
||||
-webkit-appearance: textfield; // 2
|
||||
}
|
||||
|
||||
// 1. A few input types should stay LTR
|
||||
@ -561,40 +548,39 @@ legend {
|
||||
// Remove the inner padding in Chrome and Safari on macOS.
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
// Remove padding around color pickers in webkit browsers
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
// Inherit font family and line height for file input buttons
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
// 1. Change font properties to `inherit`
|
||||
// 2. Correct the inability to style clickable types in iOS and Safari.
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit; // 1
|
||||
-webkit-appearance: button; // 2
|
||||
font: inherit; // 1
|
||||
-webkit-appearance: button; // 2
|
||||
}
|
||||
|
||||
// Correct element displays
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Remove border from iframe
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
// Summary
|
||||
@ -602,24 +588,22 @@ iframe {
|
||||
// 1. Add the correct display in all browsers
|
||||
|
||||
summary {
|
||||
display: list-item; // 1
|
||||
cursor: pointer;
|
||||
display: list-item; // 1
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
// Progress
|
||||
//
|
||||
// Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
|
||||
// Hidden attribute
|
||||
//
|
||||
// Always hide an element with the `hidden` HTML attribute.
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -390,7 +390,7 @@ $enable-cssgrid: true !default;
|
||||
$enable-button-pointers: true !default;
|
||||
$enable-rfs: true !default;
|
||||
$enable-validation-icons: true !default;
|
||||
$enable-negative-margins: false !default;
|
||||
$enable-negative-margins: true !default;
|
||||
$enable-deprecation-messages: true !default;
|
||||
$enable-important-utilities: true !default;
|
||||
|
||||
|
@ -303,6 +303,48 @@
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&.icon-small-room {
|
||||
background: url("../images/icons/small-room.png");
|
||||
width: 17px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.icon-cog {
|
||||
background: url("../images/icons/cog.png");
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-chat-history {
|
||||
background: url("../images/icons/chat-history.png");
|
||||
width: 17px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
&.icon-room-link {
|
||||
background: url("../images/icons/room-link.png");
|
||||
width: 17px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
&.icon-zoom-more {
|
||||
background: url("../images/icons/zoom-more.png");
|
||||
width: 12px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
&.icon-zoom-less {
|
||||
background: url("../images/icons/zoom-less.png");
|
||||
width: 12px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
&.icon-like-room {
|
||||
background: url("../images/icons/like-room.png");
|
||||
width: 20px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
&.icon-arrows {
|
||||
background: url("../images/icons/arrows.png");
|
||||
width: 17px;
|
||||
|
6
src/events/hc-center/HcCenterEvent.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
|
||||
export class HcCenterEvent extends NitroEvent
|
||||
{
|
||||
public static TOGGLE_HC_CENTER: string = 'HCC_TOGGLE';
|
||||
}
|
@ -66,6 +66,7 @@ $nitro-card-tabs-height: 33px;
|
||||
|
||||
.nitro-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: calc(100% - #{$toolbar-height});
|
||||
margin: 0;
|
||||
|
@ -1,26 +1,28 @@
|
||||
@import './shared/Shared';
|
||||
@import './avatar-editor/AvatarEditorView';
|
||||
@import './camera/CameraWidgetView';
|
||||
@import './catalog/CatalogView';
|
||||
@import './friends/FriendsView';
|
||||
@import './groups/GroupView';
|
||||
@import './hotel-view/HotelView';
|
||||
@import './loading/LoadingView';
|
||||
@import './main/MainView';
|
||||
@import './navigator/NavigatorView';
|
||||
@import './notification-center/NotificationCenterView';
|
||||
@import './purse/PurseView';
|
||||
@import './right-side/RightSideView';
|
||||
@import './room/RoomView';
|
||||
@import './room-host/RoomHostView';
|
||||
@import './toolbar/ToolbarView';
|
||||
@import './wired/WiredView';
|
||||
@import './mod-tools/ModToolsView';
|
||||
@import './achievements/AchievementsView';
|
||||
@import './user-settings/UserSettingsView';
|
||||
@import './user-profile/UserProfileVew';
|
||||
@import './chat-history/ChatHistoryView';
|
||||
@import './help/HelpView';
|
||||
@import './floorplan-editor/FloorplanEditorView';
|
||||
@import './nitropedia/NitropediaView';
|
||||
@import "./shared/Shared";
|
||||
@import "./avatar-editor/AvatarEditorView";
|
||||
@import "./camera/CameraWidgetView";
|
||||
@import "./catalog/CatalogView";
|
||||
@import "./friends/FriendsView";
|
||||
@import "./groups/GroupView";
|
||||
@import "./hotel-view/HotelView";
|
||||
@import "./inventory/InventoryView";
|
||||
@import "./loading/LoadingView";
|
||||
@import "./main/MainView";
|
||||
@import "./navigator/NavigatorView";
|
||||
@import "./notification-center/NotificationCenterView";
|
||||
@import "./purse/PurseView";
|
||||
@import "./right-side/RightSideView";
|
||||
@import "./room/RoomView";
|
||||
@import "./room-host/RoomHostView";
|
||||
@import "./toolbar/ToolbarView";
|
||||
@import "./wired/WiredView";
|
||||
@import "./mod-tools/ModToolsView";
|
||||
@import "./achievements/AchievementsView";
|
||||
@import "./user-settings/UserSettingsView";
|
||||
@import "./user-profile/UserProfileVew";
|
||||
@import "./chat-history/ChatHistoryView";
|
||||
@import "./help/HelpView";
|
||||
@import "./floorplan-editor/FloorplanEditorView";
|
||||
@import "./nitropedia/NitropediaView";
|
||||
@import "./hc-center/HcCenterView.scss";
|
||||
@import './campaign/CampaignView';
|
||||
|
@ -169,13 +169,14 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutVipBuyViewProps> = props =
|
||||
</div>
|
||||
</NitroCardGridItemView>
|
||||
);
|
||||
}) }
|
||||
})}
|
||||
<div className="mt-auto text-black">{ LocalizeText('catalog.vip.buy.hccenter') }</div>
|
||||
</NitroCardGridView>
|
||||
</NitroLayoutGridColumn>
|
||||
<NitroLayoutGridColumn size={ 5 }>
|
||||
<NitroLayoutFlexColumn className="justify-content-center align-items-center h-100" overflow="hidden" gap={ 2 }>
|
||||
{ GetCatalogPageImage(pageParser, 1) && <img className="" alt="" src={ GetCatalogPageImage(pageParser, 1) } /> }
|
||||
<NitroLayoutBase className="text-center text-black" overflow="auto" dangerouslySetInnerHTML={ { __html: getSubscriptionDetails } } />
|
||||
<NitroLayoutBase className="text-center text-black" overflow="auto" dangerouslySetInnerHTML={{ __html: getSubscriptionDetails }} />
|
||||
</NitroLayoutFlexColumn>
|
||||
{ pendingOffer && <div className="mt-auto w-100 text-black">
|
||||
<div className="d-flex gap-2 mb-2 align-items-center">
|
||||
|
@ -80,18 +80,19 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
parent={props.parent}
|
||||
rowIndex={props.index}
|
||||
>
|
||||
<div key={props.key} style={props.style} className="row chathistory-entry justify-content-start">
|
||||
<div key={props.key} style={props.style} className="chathistory-entry justify-content-start">
|
||||
{(item.type === ChatEntryType.TYPE_CHAT) &&
|
||||
<div className={`col d-flex flex-wrap text-break text-wrap ${isDark ? 'dark' : 'light'}`}>
|
||||
<div className="p-1">{item.timestamp}</div>
|
||||
<div className="p-1 fw-bold cursor-pointer" dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
|
||||
<div className="p-1 text-break text-wrap">{item.message}</div>
|
||||
<div className={`p-1 d-flex gap-1 ${isDark ? 'dark' : 'light'}`}>
|
||||
<div className="text-muted">{item.timestamp}</div>
|
||||
<div className="cursor-pointer d-flex text-nowrap" dangerouslySetInnerHTML={ { __html: (item.name + ':') }} />
|
||||
<div className="text-break text-wrap flex-grow-1">{item.message}</div>
|
||||
</div>
|
||||
}
|
||||
{(item.type === ChatEntryType.TYPE_ROOM_INFO) &&
|
||||
<div className={`col d-flex flex-wrap text-break text-wrap ${isDark ? 'dark' : 'light'}`}>
|
||||
<div className="p-1">{item.timestamp}</div>
|
||||
<div className="p-1 fw-bold cursor-pointer">{item.name}</div>
|
||||
<div className={`p-1 d-flex gap-1 ${isDark ? 'dark' : 'light'}`}>
|
||||
<div className="text-muted">{item.timestamp}</div>
|
||||
<i className="icon icon-small-room" />
|
||||
<div className="cursor-pointer text-break text-wrap">{item.name}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -134,9 +135,9 @@ export const ChatHistoryView: FC<{}> = props =>
|
||||
<ChatHistoryContextProvider value={ { chatHistoryState, roomHistoryState } }>
|
||||
<ChatHistoryMessageHandler />
|
||||
{isVisible &&
|
||||
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history" simple={ false } theme={'dark'} >
|
||||
<NitroCardView uniqueKey="chat-history" className="nitro-chat-history" simple={ false } theme={'dark'} >
|
||||
<NitroCardHeaderView headerText={ 'Chat History' } onCloseClick={ event => setIsVisible(false) } theme={'dark'}/>
|
||||
<NitroCardContentView className="chat-history-content" theme={'dark'}>
|
||||
<NitroCardContentView className="chat-history-content p-0" theme={'dark'}>
|
||||
<div className="row w-100 h-100 chat-history-container">
|
||||
<AutoSizer defaultWidth={300} defaultHeight={200} onResize={onResize}>
|
||||
{({ height, width }) =>
|
||||
|
@ -122,7 +122,7 @@ export const FloorplanEditorView: FC<{}> = props =>
|
||||
<>
|
||||
<FloorplanEditorContextProvider value={{ originalFloorplanSettings: originalFloorplanSettings, setOriginalFloorplanSettings: setOriginalFloorplanSettings, visualizationSettings: visualizationSettings, setVisualizationSettings: setVisualizationSettings }}>
|
||||
{isVisible &&
|
||||
<NitroCardView className="nitro-floorplan-editor">
|
||||
<NitroCardView uniqueKey="floorpan-editor" className="nitro-floorplan-editor" simple={ true }>
|
||||
<NitroCardHeaderView headerText={LocalizeText('floor.plan.editor.title')} onCloseClick={() => setIsVisible(false)} />
|
||||
<NitroCardContentView>
|
||||
<NitroLayoutGrid>
|
||||
|
@ -30,7 +30,7 @@ export class FloorplanEditor extends PixiApplicationProxy
|
||||
super({
|
||||
width: width,
|
||||
height: height,
|
||||
backgroundColor: 0x2b2b2b,
|
||||
backgroundColor: 0x000000,
|
||||
antialias: true,
|
||||
autoDensity: true,
|
||||
resolution: 1,
|
||||
|
@ -105,7 +105,9 @@ export const FloorplanCanvasView: FC<{}> = props =>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="align-items-center justify-content-center">
|
||||
<div className="arrow-button"><button className="btn btn-primary" onClick={() => onClickArrowButton('left')}><i className="fas fa-arrow-left"/></button></div>
|
||||
<div ref={elementRef} className="editor-area" />
|
||||
<div className="rounded-2 overflow-hidden">
|
||||
<div ref={elementRef} className="editor-area" />
|
||||
</div>
|
||||
<div className="arrow-button"><button className="btn btn-primary" onClick={() => onClickArrowButton('right')}><i className="fas fa-arrow-right"/></button></div>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="align-items-center justify-content-center">
|
||||
|
5
src/views/friends/common/GroupType.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class GroupType
|
||||
{
|
||||
public static readonly GROUP_CHAT = 0;
|
||||
public static readonly PRIVATE_CHAT = 1;
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { LocalizeText } from '../../../api';
|
||||
import { GroupType } from './GroupType';
|
||||
import { MessengerFriend } from './MessengerFriend';
|
||||
import { MessengerThreadChat } from './MessengerThreadChat';
|
||||
import { MessengerThreadChatGroup } from './MessengerThreadChatGroup';
|
||||
import { getGroupChatData } from './Utils';
|
||||
|
||||
export class MessengerThread
|
||||
{
|
||||
@ -10,35 +12,40 @@ export class MessengerThread
|
||||
private _participant: MessengerFriend;
|
||||
private _groups: MessengerThreadChatGroup[];
|
||||
private _lastUpdated: Date;
|
||||
private _unread: boolean;
|
||||
private _unreadCount: number;
|
||||
|
||||
constructor(participant: MessengerFriend, isNew: boolean = true)
|
||||
{
|
||||
this._participant = participant;
|
||||
this._groups = [];
|
||||
this._lastUpdated = new Date();
|
||||
this._unread = false;
|
||||
this._unreadCount = 0;
|
||||
|
||||
if(isNew)
|
||||
{
|
||||
this.addMessage(-1, LocalizeText('messenger.moderationinfo'), 0, null, MessengerThreadChat.SECURITY_NOTIFICATION);
|
||||
this.addMessage(null, LocalizeText('messenger.moderationinfo'), 0, null, MessengerThreadChat.SECURITY_NOTIFICATION);
|
||||
|
||||
this._unread = false;
|
||||
this._unreadCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public addMessage(senderId: number, message: string, secondsSinceSent: number = 0, extraData: string = null, type: number = 0): MessengerThreadChat
|
||||
{
|
||||
const group = this.getLastGroup(senderId);
|
||||
const isGroupChat = (senderId < 0 && extraData);
|
||||
const userId = isGroupChat ? getGroupChatData(extraData).userId : senderId;
|
||||
|
||||
const group = this.getLastGroup(userId);
|
||||
|
||||
if(!group) return;
|
||||
|
||||
if(isGroupChat) group.type = GroupType.GROUP_CHAT;
|
||||
|
||||
const chat = new MessengerThreadChat(senderId, message, secondsSinceSent, extraData, type);
|
||||
|
||||
group.addChat(chat);
|
||||
|
||||
this._lastUpdated = new Date();
|
||||
this._unread = true;
|
||||
this._unreadCount++;
|
||||
|
||||
return chat;
|
||||
}
|
||||
@ -58,7 +65,7 @@ export class MessengerThread
|
||||
|
||||
public setRead(): void
|
||||
{
|
||||
this._unread = false;
|
||||
this._unreadCount = 0;
|
||||
}
|
||||
|
||||
public get participant(): MessengerFriend
|
||||
@ -76,8 +83,13 @@ export class MessengerThread
|
||||
return this._lastUpdated;
|
||||
}
|
||||
|
||||
public get unreadCount(): number
|
||||
{
|
||||
return this._unreadCount;
|
||||
}
|
||||
|
||||
public get unread(): boolean
|
||||
{
|
||||
return this._unread;
|
||||
return this._unreadCount > 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { GroupType } from './GroupType';
|
||||
import { MessengerThreadChat } from './MessengerThreadChat';
|
||||
|
||||
export class MessengerThreadChatGroup
|
||||
{
|
||||
private _userId: number;
|
||||
private _chats: MessengerThreadChat[];
|
||||
private _type: number;
|
||||
|
||||
constructor(userId: number)
|
||||
constructor(userId: number, type = GroupType.PRIVATE_CHAT)
|
||||
{
|
||||
this._userId = userId;
|
||||
this._chats = [];
|
||||
this._type = type;
|
||||
}
|
||||
|
||||
public addChat(message: MessengerThreadChat): void
|
||||
@ -25,4 +28,14 @@ export class MessengerThreadChatGroup
|
||||
{
|
||||
return this._chats;
|
||||
}
|
||||
|
||||
public get type(): number
|
||||
{
|
||||
return this._type;
|
||||
}
|
||||
|
||||
public set type(type: number)
|
||||
{
|
||||
this._type = type;
|
||||
}
|
||||
}
|
||||
|
18
src/views/friends/common/Utils.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export const getGroupChatData = (extraData: string) =>
|
||||
{
|
||||
const splitData = extraData.split('/');
|
||||
|
||||
const username = splitData[0];
|
||||
const figure = splitData[1];
|
||||
const userId = parseInt(splitData[2]);
|
||||
|
||||
const result: IGroupChatData = { username: username, figure: figure, userId: userId }
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface IGroupChatData
|
||||
{
|
||||
username: string;
|
||||
figure: string;
|
||||
userId: number;
|
||||
}
|
@ -11,8 +11,15 @@
|
||||
}
|
||||
|
||||
.friend-bar-item-head {
|
||||
top: -30px;
|
||||
left: -30px;
|
||||
&.avatar {
|
||||
top: -30px;
|
||||
left: -30px;
|
||||
}
|
||||
&.group {
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { GetUserProfile, LocalizeText, OpenMessengerChat } from '../../../../api
|
||||
import { SendMessageHook } from '../../../../hooks/messages';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
||||
import { FriendBarItemViewProps } from './FriendBarItemView.types';
|
||||
|
||||
export const FriendBarItemView: FC<FriendBarItemViewProps> = props =>
|
||||
@ -58,8 +59,9 @@ export const FriendBarItemView: FC<FriendBarItemViewProps> = props =>
|
||||
|
||||
return (
|
||||
<div ref={ elementRef } className={'btn btn-success friend-bar-item ' + (isVisible ? 'friend-bar-item-active' : '')} onClick={ event => setVisible(prevValue => !prevValue) }>
|
||||
<div className="friend-bar-item-head position-absolute">
|
||||
<AvatarImageView headOnly={ true } figure={ friend.figure } direction={ 2 } />
|
||||
<div className={`friend-bar-item-head position-absolute ${friend.id > 0 ? 'avatar': 'group'}`}>
|
||||
{ friend.id > 0 && <AvatarImageView headOnly={ true } figure={ friend.figure } direction={ 2 } /> }
|
||||
{ friend.id <= 0 && <BadgeImageView isGroup={ true } badgeCode={ friend.figure} />}
|
||||
</div>
|
||||
<div className="text-truncate">{ friend.name }</div>
|
||||
{ isVisible &&
|
||||
|
@ -1,18 +1,31 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useMemo } from 'react';
|
||||
import { GetSessionDataManager } from '../../../../api';
|
||||
import { NitroLayoutFlex } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||
import { GroupType } from '../../common/GroupType';
|
||||
import { MessengerThreadChat } from '../../common/MessengerThreadChat';
|
||||
import { getGroupChatData } from '../../common/Utils';
|
||||
import { FriendsMessengerThreadGroupProps } from './FriendsMessengerThreadGroup.types';
|
||||
|
||||
export const FriendsMessengerThreadGroup: FC<FriendsMessengerThreadGroupProps> = props =>
|
||||
{
|
||||
const { thread = null, group = null } = props;
|
||||
|
||||
const isOwnChat = useMemo(() =>
|
||||
{
|
||||
if(!thread || !group) return false;
|
||||
|
||||
if(group.type === GroupType.PRIVATE_CHAT && (group.userId === GetSessionDataManager().userId)) return true;
|
||||
|
||||
if( (group.type === GroupType.GROUP_CHAT) && (group.chats.length && getGroupChatData(group.chats[0].extraData).userId === GetSessionDataManager().userId)) return true;
|
||||
|
||||
return false;
|
||||
}, [group, thread]);
|
||||
|
||||
if(!thread || !group) return null;
|
||||
|
||||
if(group.userId === -1)
|
||||
if(!group.userId)
|
||||
{
|
||||
return (
|
||||
<div className="d-flex gap-2 w-100 justify-content-start">
|
||||
@ -33,15 +46,26 @@ export const FriendsMessengerThreadGroup: FC<FriendsMessengerThreadGroupProps> =
|
||||
}
|
||||
|
||||
return (
|
||||
<NitroLayoutFlex className={ 'w-100 justify-content-' + (group.userId === 0 ? 'end' : 'start') } gap={ 2 }>
|
||||
{ (group.userId > 0) &&
|
||||
<NitroLayoutFlex className={ 'w-100 justify-content-' + (isOwnChat ? 'end' : 'start') } gap={ 2 }>
|
||||
<NitroLayoutBase className="message-avatar flex-shrink-0">
|
||||
{ (group.type === GroupType.PRIVATE_CHAT && !isOwnChat) &&
|
||||
<AvatarImageView figure={ thread.participant.figure } direction={ 2 } />
|
||||
</NitroLayoutBase> }
|
||||
<NitroLayoutBase className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (group.userId === 0 ? 'right' : 'left') }>
|
||||
{ group.chats.map((chat, index) => <NitroLayoutBase key={ index } className="text-break">{ chat.message }</NitroLayoutBase>) }
|
||||
}
|
||||
{ (group.type === GroupType.GROUP_CHAT && !isOwnChat) &&
|
||||
<AvatarImageView figure={ getGroupChatData(group.chats[0].extraData).figure } direction={ 2} />
|
||||
}
|
||||
</NitroLayoutBase>
|
||||
<NitroLayoutBase className={ 'bg-light text-black border-radius mb-2 rounded py-1 px-2 messages-group-' + (isOwnChat ? 'right' : 'left') }>
|
||||
<NitroLayoutBase className='fw-bold'>
|
||||
{
|
||||
(isOwnChat) && GetSessionDataManager().userName
|
||||
}
|
||||
{ (!isOwnChat) && ((group.type === GroupType.GROUP_CHAT) ? getGroupChatData(group.chats[0].extraData).username : thread.participant.name)
|
||||
}
|
||||
</NitroLayoutBase>
|
||||
{ group.chats.map((chat, index) =><NitroLayoutBase key={ index } className="text-break">{ chat.message }</NitroLayoutBase>) }
|
||||
</NitroLayoutBase>
|
||||
{ (group.userId === 0) &&
|
||||
{ (isOwnChat) &&
|
||||
<NitroLayoutBase className="message-avatar flex-shrink-0">
|
||||
<AvatarImageView figure={ GetSessionDataManager().figure } direction={ 4 } />
|
||||
</NitroLayoutBase> }
|
||||
|
@ -1,24 +1,27 @@
|
||||
.nitro-friends-messenger {
|
||||
width: 280px;
|
||||
resize: both;
|
||||
width: $messenger-width;
|
||||
height: $messenger-height;
|
||||
|
||||
.friend-head {
|
||||
.open-chat-entry {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
border: 2px solid;
|
||||
border-color: $light;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
z-index: 10;
|
||||
&.active {
|
||||
border-color: #fffde9;
|
||||
background-color: #ececec
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
margin-left: -27px;
|
||||
margin-top: -27px;
|
||||
.friend-head {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
overflow: hidden;
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
margin-left: -27px;
|
||||
margin-top: -27px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,8 +30,6 @@
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
height: 200px;
|
||||
min-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.message-avatar {
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { FollowFriendMessageComposer, ILinkEventTracker, NewConsoleMessageEvent, SendMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AddEventLinkTracker, GetUserProfile, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
|
||||
import { AddEventLinkTracker, GetSessionDataManager, GetUserProfile, LocalizeText, RemoveLinkEventTracker } from '../../../../api';
|
||||
import { MESSENGER_MESSAGE_RECEIVED, MESSENGER_NEW_THREAD, PlaySound } from '../../../../api/utils/PlaySound';
|
||||
import { FriendsMessengerIconEvent } from '../../../../events';
|
||||
import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlex, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../../../layout';
|
||||
import { NitroLayoutBase } from '../../../../layout/base';
|
||||
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
|
||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
||||
import { ItemCountView } from '../../../shared/item-count/ItemCountView';
|
||||
import { MessengerThread } from '../../common/MessengerThread';
|
||||
import { MessengerThreadChat } from '../../common/MessengerThreadChat';
|
||||
import { useFriendsContext } from '../../context/FriendsContext';
|
||||
@ -14,41 +16,41 @@ import { FriendsMessengerThreadView } from '../messenger-thread/FriendsMessenger
|
||||
|
||||
export const FriendsMessengerView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ messageThreads, setMessageThreads ] = useState<MessengerThread[]>([]);
|
||||
const [ activeThreadIndex, setActiveThreadIndex ] = useState(-1);
|
||||
const [ hiddenThreadIndexes, setHiddenThreadIndexes ] = useState<number[]>([]);
|
||||
const [ messageText, setMessageText ] = useState('');
|
||||
const [ updateValue, setUpdateValue ] = useState({});
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [messageThreads, setMessageThreads] = useState<MessengerThread[]>([]);
|
||||
const [activeThreadIndex, setActiveThreadIndex] = useState(-1);
|
||||
const [hiddenThreadIndexes, setHiddenThreadIndexes] = useState<number[]>([]);
|
||||
const [messageText, setMessageText] = useState('');
|
||||
const [updateValue, setUpdateValue] = useState({});
|
||||
const { friends = [] } = useFriendsContext();
|
||||
const messagesBox = useRef<HTMLDivElement>();
|
||||
|
||||
const followFriend = useCallback(() =>
|
||||
{
|
||||
SendMessageHook(new FollowFriendMessageComposer(messageThreads[activeThreadIndex].participant.id));
|
||||
}, [ messageThreads, activeThreadIndex ]);
|
||||
}, [messageThreads, activeThreadIndex]);
|
||||
|
||||
const openProfile = useCallback(() =>
|
||||
{
|
||||
GetUserProfile(messageThreads[activeThreadIndex].participant.id);
|
||||
}, [ messageThreads, activeThreadIndex ]);
|
||||
}, [messageThreads, activeThreadIndex]);
|
||||
|
||||
const getFriend = useCallback((userId: number) =>
|
||||
{
|
||||
return ((friends.find(friend => (friend.id === userId))) || null);
|
||||
}, [ friends ]);
|
||||
}, [friends]);
|
||||
|
||||
const visibleThreads = useMemo(() =>
|
||||
{
|
||||
return messageThreads.filter((thread, index) =>
|
||||
{
|
||||
if(hiddenThreadIndexes.indexOf(index) >= 0) return false;
|
||||
{
|
||||
if(hiddenThreadIndexes.indexOf(index) >= 0) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [ messageThreads, hiddenThreadIndexes ]);
|
||||
return true;
|
||||
});
|
||||
}, [messageThreads, hiddenThreadIndexes]);
|
||||
|
||||
const getMessageThreadWithIndex = useCallback<(userId: number) => [ number, MessengerThread ]>((userId: number) =>
|
||||
const getMessageThreadWithIndex = useCallback<(userId: number) => [number, MessengerThread]>((userId: number) =>
|
||||
{
|
||||
if(messageThreads.length > 0)
|
||||
{
|
||||
@ -63,43 +65,43 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
if(hiddenIndex >= 0)
|
||||
{
|
||||
setHiddenThreadIndexes(prevValue =>
|
||||
{
|
||||
const newIndexes = [ ...prevValue ];
|
||||
{
|
||||
const newIndexes = [...prevValue];
|
||||
|
||||
newIndexes.splice(hiddenIndex, 1);
|
||||
newIndexes.splice(hiddenIndex, 1);
|
||||
|
||||
return newIndexes;
|
||||
});
|
||||
return newIndexes;
|
||||
});
|
||||
}
|
||||
|
||||
return [ i, thread ];
|
||||
return [i, thread];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const friend = getFriend(userId);
|
||||
|
||||
if(!friend) return [ -1, null ];
|
||||
if(!friend) return [-1, null];
|
||||
|
||||
const thread = new MessengerThread(friend);
|
||||
const newThreads = [ ...messageThreads, thread ];
|
||||
const newThreads = [...messageThreads, thread];
|
||||
|
||||
setMessageThreads(newThreads);
|
||||
|
||||
return [ (newThreads.length - 1), thread ];
|
||||
}, [ messageThreads, hiddenThreadIndexes, getFriend ]);
|
||||
return [(newThreads.length - 1), thread];
|
||||
}, [messageThreads, hiddenThreadIndexes, getFriend]);
|
||||
|
||||
const onNewConsoleMessageEvent = useCallback((event: NewConsoleMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
const [ threadIndex, thread ] = getMessageThreadWithIndex(parser.senderId);
|
||||
const [threadIndex, thread] = getMessageThreadWithIndex(parser.senderId);
|
||||
|
||||
if((threadIndex === -1) || !thread) return;
|
||||
|
||||
thread.addMessage(parser.senderId, parser.messageText, parser.secondsSinceSent, parser.extraData);
|
||||
|
||||
setMessageThreads(prevValue => [ ...prevValue ]);
|
||||
}, [ getMessageThreadWithIndex ]);
|
||||
setMessageThreads(prevValue => [...prevValue]);
|
||||
}, [getMessageThreadWithIndex]);
|
||||
|
||||
CreateMessageHook(NewConsoleMessageEvent, onNewConsoleMessageEvent);
|
||||
|
||||
@ -117,21 +119,21 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
|
||||
if(messageThreads.length === 1 && thread.groups.length === 1) PlaySound(MESSENGER_NEW_THREAD);
|
||||
|
||||
thread.addMessage(0, messageText, 0, null, MessengerThreadChat.CHAT);
|
||||
thread.addMessage(GetSessionDataManager().userId, messageText, 0, null, MessengerThreadChat.CHAT);
|
||||
|
||||
BatchUpdates(() =>
|
||||
{
|
||||
setMessageThreads(prevValue => [ ...prevValue ]);
|
||||
setMessageThreads(prevValue => [...prevValue]);
|
||||
setMessageText('');
|
||||
});
|
||||
}, [ messageThreads, activeThreadIndex, messageText ]);
|
||||
}, [messageThreads, activeThreadIndex, messageText]);
|
||||
|
||||
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) =>
|
||||
{
|
||||
if(event.key !== 'Enter') return;
|
||||
|
||||
sendMessage();
|
||||
}, [ sendMessage ]);
|
||||
}, [sendMessage]);
|
||||
|
||||
const linkReceived = useCallback((url: string) =>
|
||||
{
|
||||
@ -146,7 +148,7 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
return;
|
||||
}
|
||||
|
||||
const [ threadIndex ] = getMessageThreadWithIndex(parseInt(parts[2]));
|
||||
const [threadIndex] = getMessageThreadWithIndex(parseInt(parts[2]));
|
||||
|
||||
if(threadIndex === -1) return;
|
||||
|
||||
@ -155,18 +157,18 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
setActiveThreadIndex(threadIndex);
|
||||
setIsVisible(true);
|
||||
});
|
||||
}, [ getMessageThreadWithIndex ]);
|
||||
}, [getMessageThreadWithIndex]);
|
||||
|
||||
const closeThread = useCallback((threadIndex: number) =>
|
||||
{
|
||||
setHiddenThreadIndexes(prevValue =>
|
||||
{
|
||||
const values = [ ...prevValue ];
|
||||
{
|
||||
const values = [...prevValue];
|
||||
|
||||
if(values.indexOf(threadIndex) === -1) values.push(threadIndex);
|
||||
if(values.indexOf(threadIndex) === -1) values.push(threadIndex);
|
||||
|
||||
return values;
|
||||
});
|
||||
return values;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
@ -179,19 +181,19 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
AddEventLinkTracker(linkTracker);
|
||||
|
||||
return () => RemoveLinkEventTracker(linkTracker);
|
||||
}, [ linkReceived ]);
|
||||
}, [linkReceived]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isVisible) return;
|
||||
|
||||
if(activeThreadIndex === -1) setActiveThreadIndex(0);
|
||||
}, [ isVisible, activeThreadIndex ]);
|
||||
}, [isVisible, activeThreadIndex]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(hiddenThreadIndexes.indexOf(activeThreadIndex) >= 0) setActiveThreadIndex(0);
|
||||
}, [ activeThreadIndex, hiddenThreadIndexes ]);
|
||||
}, [activeThreadIndex, hiddenThreadIndexes]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -205,7 +207,7 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
activeThread.setRead();
|
||||
setUpdateValue({});
|
||||
}
|
||||
}, [ isVisible, messageThreads, activeThreadIndex ]);
|
||||
}, [isVisible, messageThreads, activeThreadIndex]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -222,7 +224,7 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
|
||||
for(const thread of visibleThreads)
|
||||
{
|
||||
if(thread.unread)
|
||||
if(thread.unreadCount > 0)
|
||||
{
|
||||
isUnread = true;
|
||||
|
||||
@ -233,63 +235,71 @@ export const FriendsMessengerView: FC<{}> = props =>
|
||||
if(isUnread) PlaySound(MESSENGER_MESSAGE_RECEIVED);
|
||||
|
||||
dispatchUiEvent(new FriendsMessengerIconEvent(FriendsMessengerIconEvent.UPDATE_ICON, isUnread ? FriendsMessengerIconEvent.UNREAD_ICON : FriendsMessengerIconEvent.SHOW_ICON));
|
||||
}, [ visibleThreads, updateValue ]);
|
||||
}, [visibleThreads, updateValue]);
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-friends-messenger" uniqueKey="nitro-friends-messenger" simple={ true }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('messenger.window.title', [ 'OPEN_CHAT_COUNT' ], [ visibleThreads.length.toString() ]) } onCloseClick={ event => setIsVisible(false) } />
|
||||
<NitroCardView className="nitro-friends-messenger" uniqueKey="nitro-friends-messenger" simple={true}>
|
||||
<NitroCardHeaderView headerText={LocalizeText('messenger.window.title', ['OPEN_CHAT_COUNT'], [visibleThreads.length.toString()])} onCloseClick={event => setIsVisible(false)} />
|
||||
<NitroCardContentView>
|
||||
<NitroLayoutFlex gap={ 2 } overflow="auto">
|
||||
{ visibleThreads && (visibleThreads.length > 0) && visibleThreads.map((thread, index) =>
|
||||
<NitroLayoutGrid>
|
||||
<NitroLayoutGridColumn size={ 4 }>
|
||||
<NitroLayoutBase className='text-black fw-bold fs-5'>{LocalizeText('toolbar.icon.label.messenger')}</NitroLayoutBase>
|
||||
<NitroLayoutFlexColumn className='h-100 overflow-auto'>
|
||||
{visibleThreads && (visibleThreads.length > 0) && visibleThreads.map((thread, index) =>
|
||||
{
|
||||
const messageThreadIndex = messageThreads.indexOf(thread);
|
||||
|
||||
return (
|
||||
<div key={ index } className="position-relative friend-head rounded flex-shrink-0 cursor-pointer bg-muted" onClick={ event => setActiveThreadIndex(messageThreadIndex) }>
|
||||
{ thread.unread &&
|
||||
<NitroLayoutBase className="position-absolute nitro-friends-spritesheet icon-new-message top-1 end-1 z-index-1" /> }
|
||||
<AvatarImageView figure={ thread.participant.figure } headOnly={ true } direction={ 3 } />
|
||||
</div>
|
||||
<NitroLayoutFlex key={index} className={`open-chat-entry p-1 cursor-pointer rounded ${activeThreadIndex === messageThreadIndex ? 'active' : ''}`} onClick={event => setActiveThreadIndex(messageThreadIndex)}>
|
||||
{thread.unread &&
|
||||
<ItemCountView count={ thread.unreadCount }/>
|
||||
}
|
||||
<div className="friend-head rounded flex-shrink-0">
|
||||
|
||||
{thread.participant.id > 0 && <AvatarImageView figure={thread.participant.figure} headOnly={true} direction={3} />}
|
||||
{thread.participant.id <= 0 && <BadgeImageView isGroup={true} badgeCode={thread.participant.figure} />}
|
||||
|
||||
</div>
|
||||
<NitroLayoutBase className='d-flex text-truncate text-black ms-1 align-items-center'>{thread.participant.name}</NitroLayoutBase>
|
||||
</NitroLayoutFlex>
|
||||
);
|
||||
}) }
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlex className="align-items-center my-1" position="relative">
|
||||
{ (activeThreadIndex >= 0) &&
|
||||
<NitroLayoutBase className="text-black bg-light pe-2 flex-none">
|
||||
{ LocalizeText('messenger.window.separator', [ 'FRIEND_NAME' ], [ messageThreads[activeThreadIndex].participant.name ]) }
|
||||
</NitroLayoutBase> }
|
||||
<hr className="bg-dark m-0 w-100" />
|
||||
</NitroLayoutFlex>
|
||||
{ (activeThreadIndex >= 0) &&
|
||||
<>
|
||||
<NitroLayoutFlex className="mb-2" gap={ 2 }>
|
||||
<NitroLayoutButtonGroup>
|
||||
<NitroLayoutButton variant="primary" size="sm" onClick={ followFriend }>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-follow" />
|
||||
</NitroLayoutButton>
|
||||
<NitroLayoutButton variant="primary" size="sm" onClick={ openProfile }>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-profile-sm" />
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutButtonGroup>
|
||||
<NitroLayoutButton variant="danger" size="sm" onClick={ openProfile }>
|
||||
{ LocalizeText('messenger.window.button.report') }
|
||||
</NitroLayoutButton>
|
||||
<NitroLayoutButton className="ms-auto" variant="primary" size="sm" onClick={ event => closeThread(activeThreadIndex) }>
|
||||
<NitroLayoutBase className="fas fa-times" />
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlexColumn innerRef={ messagesBox } className="bg-muted p-2 rounded chat-messages mb-2">
|
||||
<FriendsMessengerThreadView thread={ messageThreads[activeThreadIndex] } />
|
||||
})}
|
||||
</NitroLayoutFlexColumn>
|
||||
<NitroLayoutFlex gap={ 2 }>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={ LocalizeText('messenger.window.input.default', [ 'FRIEND_NAME' ], [ messageThreads[activeThreadIndex].participant.name ]) } value={ messageText } onChange={ event => setMessageText(event.target.value) } onKeyDown={ onKeyDown } />
|
||||
<NitroLayoutButton variant="success" size="sm" onClick={ sendMessage }>
|
||||
{ LocalizeText('widgets.chatinput.say') }
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutFlex>
|
||||
</> }
|
||||
</NitroLayoutGridColumn>
|
||||
<NitroLayoutGridColumn size={ 8 }>
|
||||
{(activeThreadIndex >= 0) &&
|
||||
<>
|
||||
<NitroLayoutBase className='mb-2 text-black text-center fw-bold fs-6'>{LocalizeText('messenger.window.separator', ['FRIEND_NAME'], [messageThreads[activeThreadIndex].participant.name])}</NitroLayoutBase>
|
||||
<NitroLayoutFlex className="mb-2" gap={2}>
|
||||
<NitroLayoutButtonGroup>
|
||||
<NitroLayoutButton variant="primary" size="sm" onClick={followFriend}>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-follow" />
|
||||
</NitroLayoutButton>
|
||||
<NitroLayoutButton variant="primary" size="sm" onClick={openProfile}>
|
||||
<NitroLayoutBase className="nitro-friends-spritesheet icon-profile-sm" />
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutButtonGroup>
|
||||
<NitroLayoutButton variant="danger" size="sm" onClick={openProfile}>
|
||||
{LocalizeText('messenger.window.button.report')}
|
||||
</NitroLayoutButton>
|
||||
<NitroLayoutButton className="ms-auto" variant="primary" size="sm" onClick={event => closeThread(activeThreadIndex)}>
|
||||
<NitroLayoutBase className="fas fa-times" />
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutFlex>
|
||||
<NitroLayoutFlexColumn innerRef={messagesBox} className="bg-muted p-2 rounded chat-messages mb-2 h-100 w-100">
|
||||
<FriendsMessengerThreadView thread={messageThreads[activeThreadIndex]} />
|
||||
</NitroLayoutFlexColumn>
|
||||
<NitroLayoutFlex gap={2}>
|
||||
<input type="text" className="form-control form-control-sm" placeholder={LocalizeText('messenger.window.input.default', ['FRIEND_NAME'], [messageThreads[activeThreadIndex].participant.name])} value={messageText} onChange={event => setMessageText(event.target.value)} onKeyDown={onKeyDown} />
|
||||
<NitroLayoutButton variant="success" size="sm" onClick={sendMessage}>
|
||||
{LocalizeText('widgets.chatinput.say')}
|
||||
</NitroLayoutButton>
|
||||
</NitroLayoutFlex>
|
||||
</>}
|
||||
</NitroLayoutGridColumn>
|
||||
</NitroLayoutGrid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
|
@ -27,7 +27,7 @@ export const GroupSharedTabColorsView: FC<{}> = props =>
|
||||
}
|
||||
});
|
||||
|
||||
}, [ groupColors, groupColorsA, groupColorsB ]);
|
||||
}, [dispatchGroupsState, groupColors, groupColorsA, groupColorsB]);
|
||||
|
||||
const getGroupColor = useCallback((colorIndex: number) =>
|
||||
{
|
||||
@ -63,7 +63,7 @@ export const GroupSharedTabColorsView: FC<{}> = props =>
|
||||
<div className="fw-bold">{ LocalizeText('group.edit.color.primary.color') }</div>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<div className="row row-cols-18 g-0 gap-1 w-100 h-100 overflow-auto">
|
||||
{ groupColorsA && groupColorsA.map((item, index) =>
|
||||
{ groupColors && groupColorsA && groupColorsA.map((item, index) =>
|
||||
{
|
||||
return <div key={ index } className={ 'color-swatch cursor-pointer' + classNames({ ' active': groupColors[selectingColorIndex] === item.id }) } style={{ backgroundColor: '#' + item.color }} onClick={ () => selectColor(item.id) }></div>
|
||||
}) }
|
||||
|
44
src/views/hc-center/HcCenterView.scss
Normal file
@ -0,0 +1,44 @@
|
||||
.nitro-hc-center {
|
||||
width: 430px;
|
||||
resize: none;
|
||||
|
||||
.hc-logo {
|
||||
width: 213px;
|
||||
height: 37px;
|
||||
background-image: url("../../assets/images/hc-center/hc_logo.gif");
|
||||
}
|
||||
|
||||
.payday-special {
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.payday {
|
||||
width: 222px;
|
||||
height: 150px;
|
||||
background-image: url("../../assets/images/hc-center/payday.png");
|
||||
z-index: 3;
|
||||
color: #6b3502;
|
||||
}
|
||||
|
||||
.clock {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-image: url("../../assets/images/hc-center/clock.png");
|
||||
}
|
||||
|
||||
.streak-info {
|
||||
min-height: 64px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.habbo-avatar {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.benefits {
|
||||
background-image: url("../../assets/images/hc-center/benefits.png");
|
||||
background-position: right top;
|
||||
background-repeat: no-repeat;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
244
src/views/hc-center/HcCenterView.tsx
Normal file
@ -0,0 +1,244 @@
|
||||
import { ClubGiftInfoEvent, FriendlyTime, GetClubGiftInfo, RequestBadgesComposer, ScrKickbackData, ScrSendKickbackInfoMessageEvent, UserInfoEvent, UserSubscriptionEvent } from '@nitrots/nitro-renderer';
|
||||
import { BadgesEvent, FigureUpdateEvent } from '@nitrots/nitro-renderer/src';
|
||||
import { ScrGetKickbackInfoMessageComposer } from '@nitrots/nitro-renderer/src/nitro/communication/messages/outgoing/user/ScrGetKickbackInfoMessageComposer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { OverlayTrigger, Popover } from 'react-bootstrap';
|
||||
import { CreateLinkEvent, GetConfiguration, LocalizeText } from '../../api';
|
||||
import { HcCenterEvent } from '../../events/hc-center/HcCenterEvent';
|
||||
import { CreateMessageHook, SendMessageHook, useUiEvent } from '../../hooks';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../layout';
|
||||
import { AvatarImageView } from '../shared/avatar-image/AvatarImageView';
|
||||
import { BadgeImageView } from '../shared/badge-image/BadgeImageView';
|
||||
import { BadgeResolver } from './util/BadgeResolver';
|
||||
import { ClubStatus } from './util/ClubStatus';
|
||||
|
||||
|
||||
export const HcCenterView: FC<{}> = props =>
|
||||
{
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [userFigure, setUserFigure] = useState<string>(null);
|
||||
const [kickbackData, setKickbackData] = useState<ScrKickbackData>(null);
|
||||
const [clubDays, setClubDays] = useState(0);
|
||||
const [pastClubDays, setPastClubDays] = useState(0);
|
||||
const [clubPeriods, setPastClubPeriods] = useState(0);
|
||||
const [minsTillExpire, setMinsTillExpire] = useState(0);
|
||||
const [clubStatus, setClubStatus] = useState(ClubStatus.NONE);
|
||||
const [unclaimedGifts, setUnclaimedGifts] = useState(0);
|
||||
|
||||
const [badgeCode, setBadgeCode] = useState(BadgeResolver.default_badge);
|
||||
|
||||
const onUserInfoEvent = useCallback((event: UserInfoEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
setUserFigure(parser.userInfo.figure);
|
||||
}, []);
|
||||
|
||||
const onUserFigureEvent = useCallback((event: FigureUpdateEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
setUserFigure(parser.figure);
|
||||
}, []);
|
||||
|
||||
CreateMessageHook(UserInfoEvent, onUserInfoEvent);
|
||||
CreateMessageHook(FigureUpdateEvent, onUserFigureEvent);
|
||||
|
||||
const onHcCenterEvent = useCallback((event: HcCenterEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
{
|
||||
case HcCenterEvent.TOGGLE_HC_CENTER:
|
||||
setIsVisible(!isVisible);
|
||||
break;
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
useUiEvent(HcCenterEvent.TOGGLE_HC_CENTER, onHcCenterEvent);
|
||||
|
||||
const onClubGiftInfoEvent = useCallback((event: ClubGiftInfoEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setUnclaimedGifts(parser.giftsAvailable);
|
||||
}, []);
|
||||
|
||||
const onScrSendKickbackInfo = useCallback((event: ScrSendKickbackInfoMessageEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setKickbackData(parser.data)
|
||||
}, []);
|
||||
|
||||
const onBadges = useCallback((event: BadgesEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
setBadgeCode(BadgeResolver.getClubBadge(parser.getAllBadgeCodes()));
|
||||
}, [])
|
||||
|
||||
CreateMessageHook(ClubGiftInfoEvent, onClubGiftInfoEvent);
|
||||
CreateMessageHook(ScrSendKickbackInfoMessageEvent, onScrSendKickbackInfo);
|
||||
CreateMessageHook(BadgesEvent, onBadges);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageHook(new GetClubGiftInfo());
|
||||
SendMessageHook(new ScrGetKickbackInfoMessageComposer());
|
||||
SendMessageHook(new RequestBadgesComposer());
|
||||
}, []);
|
||||
|
||||
const onUserSubscriptionEvent = useCallback((event: UserSubscriptionEvent) =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
const productName = parser.productName;
|
||||
|
||||
if((productName !== 'club_habbo') && (productName !== 'habbo_club')) return;
|
||||
|
||||
setClubDays(Math.max(0, parser.daysToPeriodEnd));
|
||||
setPastClubPeriods(Math.max(0, parser.periodsSubscribedAhead));
|
||||
setPastClubDays(Math.max(0, parser.pastClubDays));
|
||||
setMinsTillExpire(Math.max(0, parser.minutesUntilExpiration));
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(clubDays > 0) return setClubStatus(ClubStatus.ACTIVE)
|
||||
if(pastClubDays > 0) return setClubStatus(ClubStatus.EXPIRED);
|
||||
|
||||
}, [clubDays, pastClubDays]);
|
||||
|
||||
CreateMessageHook(UserSubscriptionEvent, onUserSubscriptionEvent);
|
||||
|
||||
const getClubText = useCallback(() =>
|
||||
{
|
||||
const totalDays = ((clubPeriods * 31) + clubDays);
|
||||
const minutesUntilExpiration = minsTillExpire;
|
||||
|
||||
if(clubStatus !== ClubStatus.ACTIVE)
|
||||
{
|
||||
return LocalizeText('purse.clubdays.zero.amount.text');
|
||||
}
|
||||
else if((minutesUntilExpiration > -1) && (minutesUntilExpiration < (60 * 24)))
|
||||
{
|
||||
return FriendlyTime.shortFormat(minutesUntilExpiration * 60);
|
||||
}
|
||||
else
|
||||
{
|
||||
return FriendlyTime.shortFormat(totalDays * 86400);
|
||||
}
|
||||
}, [clubStatus, clubDays, clubPeriods, minsTillExpire]);
|
||||
|
||||
const getInfoText = useCallback(() =>
|
||||
{
|
||||
switch(clubStatus)
|
||||
{
|
||||
case ClubStatus.ACTIVE:
|
||||
return LocalizeText('hccenter.status.' + clubStatus + '.info',
|
||||
['timeleft', 'joindate', 'streakduration'],
|
||||
[getClubText(), kickbackData.firstSubscriptionDate, FriendlyTime.shortFormat(kickbackData.currentHcStreak * 86400)]);
|
||||
case ClubStatus.EXPIRED:
|
||||
return LocalizeText('hccenter.status.' + clubStatus + '.info', ['joindate'], [kickbackData.firstSubscriptionDate])
|
||||
default:
|
||||
return LocalizeText('hccenter.status.' + clubStatus + '.info');
|
||||
}
|
||||
}, [clubStatus, kickbackData, getClubText]);
|
||||
|
||||
const getHcPaydayTime = useCallback(() =>
|
||||
{
|
||||
if(kickbackData.timeUntilPayday < 60) return LocalizeText('hccenter.special.time.soon');
|
||||
return FriendlyTime.shortFormat(kickbackData.timeUntilPayday * 60);
|
||||
}, [kickbackData]);
|
||||
|
||||
const getHcPaydayAmount = useCallback(() =>
|
||||
{
|
||||
return LocalizeText('hccenter.special.sum', ['credits'], [(kickbackData.creditRewardForStreakBonus + kickbackData.creditRewardForMonthlySpent).toString()])
|
||||
}, [kickbackData])
|
||||
|
||||
if(!isVisible) return null;
|
||||
|
||||
const popover = (
|
||||
<Popover id="popover-basic">
|
||||
<Popover.Body className="text-black py-2 px-3">
|
||||
<h5>{LocalizeText('hccenter.breakdown.title')}</h5>
|
||||
<div>{LocalizeText('hccenter.breakdown.creditsspent', ['credits'], [kickbackData.totalCreditsSpent.toString()])}</div>
|
||||
<div>{LocalizeText('hccenter.breakdown.paydayfactor.percent', ['percent'], [(kickbackData.kickbackPercentage * 100).toString()])}</div>
|
||||
<div>{LocalizeText('hccenter.breakdown.streakbonus', ['credits'], [kickbackData.creditRewardForStreakBonus.toString()])}</div>
|
||||
<hr className="w-100 text-black my-1" />
|
||||
<div>{LocalizeText('hccenter.breakdown.total', ['credits', 'actual'], [getHcPaydayAmount(),
|
||||
((((kickbackData.kickbackPercentage * kickbackData.totalCreditsSpent) + kickbackData.creditRewardForStreakBonus) * 100) / 100).toString()])}</div>
|
||||
<div className="btn btn-link text-primary p-0" onClick={() => { CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }}>{
|
||||
LocalizeText('hccenter.special.infolink')}
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
return (
|
||||
<NitroCardView simple={true} className="nitro-hc-center">
|
||||
<NitroCardHeaderView headerText={LocalizeText('generic.hccenter')} onCloseClick={() => setIsVisible(false)} />
|
||||
<div className="bg-muted p-2 position-relative">
|
||||
<div className="hc-logo mb-2" />
|
||||
<button className="btn btn-success" onClick={ () => { CreateLinkEvent('catalog/open/' + GetConfiguration('hc.center')['catalog.buy']) } }>
|
||||
{LocalizeText(clubStatus === ClubStatus.ACTIVE ? 'hccenter.btn.extend' : 'hccenter.btn.buy')}
|
||||
</button>
|
||||
<div className="position-absolute end-0 p-4 top-0 habbo-avatar">
|
||||
<AvatarImageView figure={userFigure} direction={4} scale={2} />
|
||||
</div>
|
||||
</div>
|
||||
<NitroCardContentView>
|
||||
<div className="d-flex flex-row mb-1">
|
||||
<BadgeImageView badgeCode={badgeCode} className="align-self-center flex-shrink-0 me-1" />
|
||||
<div className="w-100 text-black streak-info">
|
||||
<h6 className="mb-0">{LocalizeText('hccenter.status.' + clubStatus)}</h6>
|
||||
<div dangerouslySetInnerHTML={{ __html: getInfoText() }} />
|
||||
</div>
|
||||
</div>
|
||||
{GetConfiguration('hc.center')['payday.info'] &&
|
||||
<div className="d-flex flex-row align-items-center mb-n2">
|
||||
<div className="rounded-start bg-primary p-2 payday-special mb-1 d-flex flex-column">
|
||||
<h4 className="mb-1">{LocalizeText('hccenter.special.title')}</h4>
|
||||
<div>{LocalizeText('hccenter.special.info')}</div>
|
||||
<div className="btn btn-link text-white p-0 mt-auto align-self-baseline" onClick={() => { CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['payday.habbopage']) }}>{LocalizeText('hccenter.special.infolink')}</div>
|
||||
</div>
|
||||
<div className="payday flex-shrink-0 p-2">
|
||||
<h5 className="mb-2 ms-2">{LocalizeText('hccenter.special.time.title')}</h5>
|
||||
<div className="d-flex flex-row mb-2">
|
||||
<div className="clock me-2" />
|
||||
<h6 className="mb-0 align-self-center">{getHcPaydayTime()}</h6>
|
||||
</div>
|
||||
{clubStatus === ClubStatus.ACTIVE &&
|
||||
<div className="pe-3">
|
||||
<h5 className="ms-2 mb-1 bolder">{LocalizeText('hccenter.special.amount.title')}</h5>
|
||||
<div className="d-flex flex-column">
|
||||
<div className="w-100 text-center ms-4n">{getHcPaydayAmount()}</div>
|
||||
<OverlayTrigger trigger="hover" placement="left" overlay={popover}>
|
||||
<div className="btn btn-link align-self-end text-primary">
|
||||
{LocalizeText('hccenter.breakdown.infolink')}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{GetConfiguration('hc.center')['gift.info'] &&
|
||||
<div className="rounded bg-success p-2 d-flex flex-row mb-0">
|
||||
<div>
|
||||
<h4 className="mb-1">{LocalizeText('hccenter.gift.title')}</h4>
|
||||
<div dangerouslySetInnerHTML={{ __html: unclaimedGifts > 0 ? LocalizeText('hccenter.unclaimedgifts', ['unclaimedgifts'], [unclaimedGifts.toString()]) : LocalizeText('hccenter.gift.info') }}></div>
|
||||
</div>
|
||||
<button className="btn btn-primary btn-lg align-self-center ms-auto" onClick={() => { CreateLinkEvent('catalog/open/' + GetConfiguration('hc.center')['catalog.gifts']) }}>{LocalizeText(clubStatus === ClubStatus.ACTIVE ? 'hccenter.btn.gifts.redeem' : 'hccenter.btn.gifts.view')}</button>
|
||||
</div>
|
||||
}
|
||||
{GetConfiguration('hc.center')['benefits.info'] &&
|
||||
<div className="benefits text-black py-2">
|
||||
<h5 className="mb-1 text-primary">{LocalizeText('hccenter.general.title')}</h5>
|
||||
<div className="mb-2" dangerouslySetInnerHTML={{ __html: LocalizeText('hccenter.general.info') }} />
|
||||
<button className="btn btn-link p-0 text-primary" onClick={() => { CreateLinkEvent('habbopages/' + GetConfiguration('hc.center')['benefits.habbopage']) }}>{LocalizeText('hccenter.general.infolink')}</button>
|
||||
</div>
|
||||
}
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
||||
}
|
23
src/views/hc-center/util/BadgeResolver.ts
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
export class BadgeResolver
|
||||
{
|
||||
public static default_badge: string = 'HC1';
|
||||
public static badges: string[] = ['ACH_VipHC1', 'ACH_VipHC2', 'ACH_VipHC3', 'ACH_VipHC4', 'ACH_VipHC5', 'HC1', 'HC2', 'HC3', 'HC4', 'HC5'];
|
||||
|
||||
|
||||
public static getClubBadge(k: string[]): string
|
||||
{
|
||||
var badgeCode: string = null;
|
||||
|
||||
this.badges.forEach(badge =>
|
||||
{
|
||||
if (k.indexOf(badge) > -1)
|
||||
{
|
||||
badgeCode = badge;
|
||||
}
|
||||
});
|
||||
|
||||
return badgeCode || this.default_badge;
|
||||
}
|
||||
|
||||
}
|
6
src/views/hc-center/util/ClubStatus.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export class ClubStatus
|
||||
{
|
||||
public static ACTIVE: string = 'active';
|
||||
public static NONE: string = 'none';
|
||||
public static EXPIRED: string = 'expired';
|
||||
}
|
@ -13,6 +13,7 @@ import { ChatHistoryView } from '../chat-history/ChatHistoryView';
|
||||
import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView';
|
||||
import { FriendsView } from '../friends/FriendsView';
|
||||
import { GroupsView } from '../groups/GroupsView';
|
||||
import { HcCenterView } from '../hc-center/HcCenterView';
|
||||
import { HelpView } from '../help/HelpView';
|
||||
import { HotelView } from '../hotel-view/HotelView';
|
||||
import { ModToolsView } from '../mod-tools/ModToolsView';
|
||||
@ -115,6 +116,7 @@ export const MainView: FC<MainViewProps> = props =>
|
||||
<HelpView />
|
||||
<FloorplanEditorView />
|
||||
<NitropediaView />
|
||||
<HcCenterView />
|
||||
<CampaignView />
|
||||
</div>
|
||||
);
|
||||
|
@ -1,21 +1,22 @@
|
||||
.nitro-purse {
|
||||
background-color: rgba($dark,.95);
|
||||
background-color: rgba($dark, 0.95);
|
||||
font-size: $font-size-sm;
|
||||
pointer-events: all;
|
||||
margin-bottom: 5px;
|
||||
padding: 6px 5px;
|
||||
box-shadow: inset 0px 5px lighten(rgba($dark,.6),2.5), inset 0 -4px darken(rgba($dark,.6),4);
|
||||
box-shadow: inset 0px 5px lighten(rgba($dark, 0.6), 2.5),
|
||||
inset 0 -4px darken(rgba($dark, 0.6), 4);
|
||||
|
||||
.notification-button {
|
||||
color:lighten($dark,20);
|
||||
color: lighten($dark, 20);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
pointer-events: all;
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nitro-purse-hc {
|
||||
background-color: rgba($light,.1);
|
||||
background-color: rgba($light, 0.1);
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
@ -29,9 +30,10 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($light,.1);
|
||||
background-color: rgba($light, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import './views';
|
||||
@import "./views";
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { FriendlyTime, HabboClubLevelEnum, UserCurrencyComposer, UserSubscriptionComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { GetConfiguration, LocalizeText } from '../../api';
|
||||
import { HcCenterEvent } from '../../events/hc-center/HcCenterEvent';
|
||||
import { HelpEvent } from '../../events/help/HelpEvent';
|
||||
import { UserSettingsUIEvent } from '../../events/user-settings/UserSettingsUIEvent';
|
||||
import { dispatchUiEvent } from '../../hooks';
|
||||
@ -30,6 +31,11 @@ export const PurseView: FC<{}> = props =>
|
||||
dispatchUiEvent(new HelpEvent(HelpEvent.TOGGLE_HELP_CENTER));
|
||||
}, []);
|
||||
|
||||
const handleHcCenterClick = useCallback(() =>
|
||||
{
|
||||
dispatchUiEvent(new HcCenterEvent(HcCenterEvent.TOGGLE_HC_CENTER));
|
||||
}, []);
|
||||
|
||||
const displayedCurrencies = useMemo(() =>
|
||||
{
|
||||
return GetConfiguration<number[]>('system.currency.types', []);
|
||||
@ -139,7 +145,8 @@ export const PurseView: FC<{}> = props =>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-4 px-0">
|
||||
<div className="nitro-purse-hc rounded mx-1 p-1 d-flex flex-column justify-content-center align-items-center h-100">
|
||||
<div className="nitro-purse-hc nitro-purse-button rounded mx-1 p-1 d-flex flex-column justify-content-center align-items-center h-100"
|
||||
onClick={ handleHcCenterClick }>
|
||||
<CurrencyIcon className="flex-shrink-0" type="hc" />
|
||||
<span>{ getClubText() }</span>
|
||||
</div>
|
||||
|
@ -1,2 +1,2 @@
|
||||
@import './currency/CurrencyView';
|
||||
@import './seasonal/SeasonalView';
|
||||
@import "./currency/CurrencyView";
|
||||
@import "./seasonal/SeasonalView";
|
||||
|
@ -1,7 +1,7 @@
|
||||
.nitro-room-tools {
|
||||
position: absolute;
|
||||
bottom: 125px;
|
||||
left: -133px;
|
||||
left: -145px;
|
||||
background: rgba($dark,.95);
|
||||
box-shadow: inset 0px 5px lighten(rgba($dark,.6),2.5), inset 0 -4px darken(rgba($dark,.6),4);
|
||||
border-top-right-radius: $border-radius;
|
||||
@ -36,4 +36,16 @@
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.tools-item {
|
||||
.icon {
|
||||
width: 22px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,21 +45,21 @@ export const RoomToolsWidgetView: FC<{}> = props =>
|
||||
|
||||
return (
|
||||
<div className={'nitro-room-tools ps-3 d-flex' + classNames({ ' open': isExpended })}>
|
||||
<div className="list-group list-group-flush w-100 me-1">
|
||||
<div className="list-group-item" onClick={ () => handleToolClick('settings') }>
|
||||
<i className="fas fa-cog me-2" />{ LocalizeText('room.settings.button.text') }
|
||||
<div className="d-flex flex-column gap-2 w-100 py-2 pe-2">
|
||||
<div className="d-flex align-items-center gap-2 cursor-pointer tools-item" onClick={ () => handleToolClick('settings') }>
|
||||
<i className="icon icon-cog" /> { LocalizeText('room.settings.button.text') }
|
||||
</div>
|
||||
<div className="list-group-item" onClick={ () => handleToolClick('zoom') }>
|
||||
<i className={ 'fas me-2 ' +classNames({ 'fa-search-minus': !isZoomedIn, 'fa-search-plus': isZoomedIn }) } />{ LocalizeText('room.zoom.button.text') }
|
||||
<div className="d-flex align-items-center gap-2 cursor-pointer tools-item" onClick={ () => handleToolClick('zoom') }>
|
||||
<i className={ 'icon ' +classNames({ 'icon-zoom-less': !isZoomedIn, 'icon-zoom-more': isZoomedIn }) } />{ LocalizeText('room.zoom.button.text') }
|
||||
</div>
|
||||
<div className="list-group-item" onClick={ () => handleToolClick('chat_history') }>
|
||||
<i className="fas fa-comment-alt me-2" />{ LocalizeText('room.chathistory.button.text') }
|
||||
<div className="d-flex align-items-center gap-2 cursor-pointer tools-item" onClick={ () => handleToolClick('chat_history') }>
|
||||
<i className="icon icon-chat-history" />{ LocalizeText('room.chathistory.button.text') }
|
||||
</div>
|
||||
<div className={ 'list-group-item' + classNames({ ' disabled': isLiked })} onClick={ () => handleToolClick('like_room') }>
|
||||
<i className="fas fa-heart me-2" />{ LocalizeText('room.like.button.text') }
|
||||
<div className={ 'd-flex align-items-center gap-2 cursor-pointer tools-item' + classNames({ ' disabled': isLiked })} onClick={ () => handleToolClick('like_room') }>
|
||||
<i className="icon icon-like-room" /> { LocalizeText('room.like.button.text') }
|
||||
</div>
|
||||
<div className="list-group-item" onClick={ () => handleToolClick('toggle_room_link') }>
|
||||
<i className="fas fa-link me-2" />{ LocalizeText('navigator.embed.caption') }
|
||||
<div className="d-flex align-items-center gap-2 cursor-pointer tools-item" onClick={ () => handleToolClick('toggle_room_link') }>
|
||||
<i className="icon icon-room-link" />{ LocalizeText('navigator.embed.caption') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="cursor-pointer d-flex align-items-center px-2" onClick={() => setIsExpanded(value => !value)}>
|
||||
|