Merge branch 'dev' into feature/guide-tool
7
.env.example
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 }}
|
29
.gitignore
vendored
@ -1,20 +1,7 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
@ -22,29 +9,23 @@ speed-measure-plugin*.json
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
*.log
|
||||
.git
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Nitro
|
||||
/build
|
||||
*.zip
|
||||
.env
|
||||
public/renderer-config.json
|
||||
public/ui-config.json
|
||||
|
1
.prettierignore
Normal file
@ -0,0 +1 @@
|
||||
*.scss
|
19384
package-lock.json
generated
17
package.json
@ -5,16 +5,19 @@
|
||||
"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 install && yarn build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@craco/craco": "^6.3.0",
|
||||
"@nitrots/nitro-renderer": "file:../nitro-renderer",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||
"@fortawesome/react-fontawesome": "^0.1.16",
|
||||
"@nitrots/nitro-renderer": "^1.1.4",
|
||||
"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",
|
||||
@ -23,20 +26,16 @@
|
||||
"react-transition-group": "^4.4.2",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"react-youtube": "^7.13.1",
|
||||
"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",
|
||||
"@types/react-slider": "^1.3.1",
|
||||
"@types/react-transition-group": "^4.4.2",
|
||||
"@types/react-virtualized": "^9.21.13",
|
||||
"@types/styled-components": "^5.1.15",
|
||||
"@typescript-eslint/eslint-plugin": "^4.29.1"
|
||||
}
|
||||
}
|
||||
|
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": "wss://ws.nitrots.co:2096",
|
||||
"asset.url": "https://nitro.nitrots.co",
|
||||
"image.library.url": "https://swf.nitrots.co/c_images/",
|
||||
"hof.furni.url": "https://swf.nitrots.co/dcr/hof_furni",
|
||||
"socket.url": "",
|
||||
"asset.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": "https://cdn.ironhotel.biz/group-badge/%badgedata%.gif",
|
||||
"badge.asset.group.external.url": "",
|
||||
"badge.asset.grouparts.url": "https://cdn.ironhotel.biz/static_iron_active/c_images/Badgeparts/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"
|
@ -1,15 +1,16 @@
|
||||
{
|
||||
"image.library.notifications.url": "${image.library.url}notifications/%image%.png",
|
||||
"achievements.images.url": "${image.library.url}Quests/%image%.png",
|
||||
"camera.url": "https://nitro.nitrots.co/camera",
|
||||
"thumbnails.url": "https://nitro.nitrots.co/camera/thumbnail/%thumbnail%.png",
|
||||
"url.prefix": "http://localhost:3000",
|
||||
"camera.url": "https://camera.com",
|
||||
"thumbnails.url": "https://camera.com/thumbnail/%thumbnail%.png",
|
||||
"url.prefix": "https://website.com",
|
||||
"floorplan.tile.url": "${asset.url}/floorplan-editor/tiles.json",
|
||||
"habbopages.url": "${url.prefix}/",
|
||||
"group.homepage.url": "${url.prefix}/groups/%groupid%/id",
|
||||
"guide.help.alpha.groupid": 0,
|
||||
"chat.viewer.height.percentage": 0.40,
|
||||
"widget.dimmer.colorwheel": false,
|
||||
"avatar.wardrobe.max.slots": 10,
|
||||
"hotelview": {
|
||||
"widgets": {
|
||||
"slot.1.widget": "promoarticle",
|
||||
@ -46,9 +47,17 @@
|
||||
"system.currency.types": [
|
||||
-1,
|
||||
0,
|
||||
5,
|
||||
101
|
||||
5
|
||||
],
|
||||
"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",
|
25
src/App.scss
@ -17,8 +17,8 @@ $grid-active-border-color: $white;
|
||||
|
||||
$toolbar-height: 55px;
|
||||
|
||||
$achievement-width: 350px;
|
||||
$achievement-height: 370px;
|
||||
$achievement-width: 375px;
|
||||
$achievement-height: 425px;
|
||||
|
||||
$avatar-editor-width: 620px;
|
||||
$avatar-editor-height: 374px;
|
||||
@ -50,16 +50,33 @@ $chat-history-height: 300px;
|
||||
$friends-list-width: 250px;
|
||||
$friends-list-height: 300px;
|
||||
|
||||
$help-width: 275px;
|
||||
$help-height: 450px;
|
||||
$help-width: 450px;
|
||||
$help-height: 250px;
|
||||
|
||||
$nitropedia-width: 400px;
|
||||
$nitropedia-height: 400px;
|
||||
|
||||
$messenger-width: 500px;
|
||||
$messenger-height: 370px;
|
||||
|
||||
$marketplace-post-offer-width: 430px;
|
||||
$marketplace-post-offer-height: 250px;
|
||||
|
||||
$camera-editor-width: 600px;
|
||||
$camera-editor-height: 500px;
|
||||
|
||||
$camera-checkout-width: 350px;
|
||||
|
||||
$room-info-width: 325px;
|
||||
|
||||
$nitro-mod-tools-width: 175px;
|
||||
|
||||
.nitro-app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@import './common';
|
||||
@import "./layout/Layout";
|
||||
@import './components';
|
||||
@import "./views/Styles";
|
||||
|
20
src/App.tsx
@ -1,13 +1,14 @@
|
||||
import { ConfigurationEvent, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent, WebGL } from '@nitrots/nitro-renderer';
|
||||
import { ConfigurationEvent, HabboWebTools, LegacyExternalInterface, Nitro, NitroCommunicationDemoEvent, NitroEvent, NitroLocalizationEvent, NitroVersion, RoomEngineEvent, WebGL } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useState } from 'react';
|
||||
import { GetCommunication, GetConfiguration, GetNitroInstance } from './api';
|
||||
import { Base } from './common';
|
||||
import { LoadingView } from './components/loading/LoadingView';
|
||||
import { MainView } from './components/main/MainView';
|
||||
import { useConfigurationEvent } from './hooks/events/core/configuration/configuration-event';
|
||||
import { useLocalizationEvent } from './hooks/events/nitro/localization/localization-event';
|
||||
import { dispatchMainEvent, useMainEvent } from './hooks/events/nitro/main-event';
|
||||
import { useRoomEngineEvent } from './hooks/events/nitro/room/room-engine-event';
|
||||
import { TransitionAnimation, TransitionAnimationTypes } from './layout';
|
||||
import { LoadingView } from './views/loading/LoadingView';
|
||||
import { MainView } from './views/main/MainView';
|
||||
|
||||
export const App: FC<{}> = props =>
|
||||
{
|
||||
@ -68,6 +69,8 @@ export const App: FC<{}> = props =>
|
||||
setMessage('Finishing Up');
|
||||
|
||||
GetNitroInstance().init();
|
||||
|
||||
if(LegacyExternalInterface.available) LegacyExternalInterface.call('legacyTrack', 'authentication', 'authok', []);
|
||||
return;
|
||||
case NitroCommunicationDemoEvent.CONNECTION_ERROR:
|
||||
setIsError(true);
|
||||
@ -79,7 +82,7 @@ export const App: FC<{}> = props =>
|
||||
setIsError(true);
|
||||
setMessage('Connection Error');
|
||||
|
||||
LegacyExternalInterface.call('disconnect', -1, 'client.init.handshake.fail');
|
||||
HabboWebTools.send(-1, 'client.init.handshake.fail');
|
||||
return;
|
||||
case RoomEngineEvent.ENGINE_INITIALIZED:
|
||||
setIsReady(true);
|
||||
@ -125,12 +128,13 @@ export const App: FC<{}> = props =>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="nitro-app overflow-hidden">
|
||||
<div id="nitro-alerts-container" />
|
||||
{ (!isReady || isError) && <LoadingView isError={ isError } message={ message } /> }
|
||||
<Base fit overflow="hidden">
|
||||
{ (!isReady || isError) &&
|
||||
<LoadingView isError={ isError } message={ message } /> }
|
||||
<TransitionAnimation type={ TransitionAnimationTypes.FADE_IN } inProp={ (isReady && !isError) }>
|
||||
<MainView />
|
||||
</TransitionAnimation>
|
||||
</div>
|
||||
<Base id="draggable-windows-container" />
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
@ -2,5 +2,7 @@ import { GetNitroInstance } from './GetNitroInstance';
|
||||
|
||||
export function CreateLinkEvent(link: string): void
|
||||
{
|
||||
link = (link.startsWith('event:') ? link.substring(6) : link);
|
||||
|
||||
GetNitroInstance().createLinkEvent(link);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { IFurnitureData, IGetImageListener, NitroEvent, NitroRenderTexture, PetFigureData, RoomObjectCategory, RoomObjectVariable, RoomSessionPresentEvent, RoomWidgetEnum, TextureUtils, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { GetSessionDataManager, IsOwnerOfFurniture } from '../../..';
|
||||
import { GetRoomEngine, LocalizeText } from '../../../..';
|
||||
import { ProductTypeEnum } from '../../../../../views/catalog/common/ProductTypeEnum';
|
||||
import { ProductTypeEnum } from '../../../../../components/catalog/common/ProductTypeEnum';
|
||||
import { RoomWidgetUpdateEvent, RoomWidgetUpdatePresentDataEvent } from '../events';
|
||||
import { RoomWidgetFurniToWidgetMessage, RoomWidgetPresentOpenMessage } from '../messages';
|
||||
import { RoomWidgetMessage } from '../messages/RoomWidgetMessage';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NitroEvent, RoomEngineUseProductEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionDanceEvent, RoomSessionUserDataUpdateEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer';
|
||||
import { GetRoomEngine, GetRoomSession, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
|
||||
import { FurniCategory } from '../../../../../views/inventory/common/FurniCategory';
|
||||
import { NitroEvent, RoomEngineUseProductEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionDanceEvent, RoomSessionPetStatusUpdateEvent, RoomSessionUserDataUpdateEvent, RoomWidgetEnum } from '@nitrots/nitro-renderer';
|
||||
import { GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
|
||||
import { FurniCategory } from '../../../../../components/inventory/common/FurniCategory';
|
||||
import { RoomWidgetAvatarInfoEvent, RoomWidgetUpdateDanceStatusEvent, RoomWidgetUpdateEvent, RoomWidgetUpdateUserDataEvent, RoomWidgetUseProductBubbleEvent, UseProductItem } from '../events';
|
||||
import { RoomWidgetAvatarExpressionMessage, RoomWidgetChangePostureMessage, RoomWidgetDanceMessage, RoomWidgetMessage, RoomWidgetRoomObjectMessage, RoomWidgetUseProductMessage, RoomWidgetUserActionMessage } from '../messages';
|
||||
import { RoomWidgetHandler } from './RoomWidgetHandler';
|
||||
@ -19,7 +19,7 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
|
||||
let isDancing = false;
|
||||
|
||||
const userData = GetRoomSession().userDataManager.getUserData(GetSessionDataManager().userId);
|
||||
const userData = this.container.roomSession.userDataManager.getUserData(GetSessionDataManager().userId);
|
||||
|
||||
if(userData && (userData.roomIndex === danceEvent.roomIndex)) isDancing = (danceEvent.danceId !== 0);
|
||||
|
||||
@ -30,6 +30,9 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
case RoomEngineUseProductEvent.USE_PRODUCT_FROM_ROOM:
|
||||
this.processUsableRoomObject((event as RoomEngineUseProductEvent).objectId);
|
||||
return;
|
||||
case RoomSessionPetStatusUpdateEvent.PET_STATUS_UPDATE:
|
||||
this.processRoomSessionPetStatusUpdateEvent((event as RoomSessionPetStatusUpdateEvent));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,28 +47,39 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
case RoomWidgetRoomObjectMessage.GET_OWN_CHARACTER_INFO:
|
||||
this.processOwnCharacterInfo();
|
||||
break;
|
||||
case RoomWidgetUserActionMessage.START_NAME_CHANGE:
|
||||
// habbo help - start name change
|
||||
break;
|
||||
case RoomWidgetUserActionMessage.REQUEST_PET_UPDATE:
|
||||
break;
|
||||
case RoomWidgetUseProductMessage.PET_PRODUCT: {
|
||||
const productMessage = (message as RoomWidgetUseProductMessage);
|
||||
|
||||
this.container.roomSession.usePetProduct(productMessage.objectId, productMessage.petId);
|
||||
break;
|
||||
}
|
||||
case RoomWidgetUserActionMessage.HARVEST_PET:
|
||||
this.container.roomSession.harvestPet(userId);
|
||||
break;
|
||||
case RoomWidgetUserActionMessage.COMPOST_PLANT:
|
||||
this.container.roomSession.compostPlant(userId);
|
||||
break;
|
||||
case RoomWidgetDanceMessage.DANCE: {
|
||||
const danceMessage = (message as RoomWidgetDanceMessage);
|
||||
|
||||
GetRoomSession().sendDanceMessage(danceMessage.style);
|
||||
this.container.roomSession.sendDanceMessage(danceMessage.style);
|
||||
break;
|
||||
}
|
||||
case RoomWidgetAvatarExpressionMessage.AVATAR_EXPRESSION: {
|
||||
const expressionMessage = (message as RoomWidgetAvatarExpressionMessage);
|
||||
|
||||
GetRoomSession().sendExpressionMessage(expressionMessage.animation.ordinal)
|
||||
this.container.roomSession.sendExpressionMessage(expressionMessage.animation.ordinal)
|
||||
break;
|
||||
}
|
||||
case RoomWidgetChangePostureMessage.CHANGE_POSTURE: {
|
||||
const postureMessage = (message as RoomWidgetChangePostureMessage);
|
||||
|
||||
GetRoomSession().sendPostureMessage(postureMessage.posture);
|
||||
break;
|
||||
}
|
||||
case RoomWidgetUseProductMessage.PET_PRODUCT: {
|
||||
const productMessage = (message as RoomWidgetUseProductMessage);
|
||||
|
||||
GetRoomSession().usePetProduct(productMessage.objectId, productMessage.petId);
|
||||
this.container.roomSession.sendPostureMessage(postureMessage.posture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -78,9 +92,11 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
const userId = GetSessionDataManager().userId;
|
||||
const userName = GetSessionDataManager().userName;
|
||||
const allowNameChange = GetSessionDataManager().canChangeName;
|
||||
const userData = GetRoomSession().userDataManager.getUserData(userId);
|
||||
const userData = this.container.roomSession.userDataManager.getUserData(userId);
|
||||
|
||||
if(userData) this.container.eventDispatcher.dispatchEvent(new RoomWidgetAvatarInfoEvent(userId, userName, userData.type, userData.roomIndex, allowNameChange));
|
||||
if(!userData) return;
|
||||
|
||||
this.container.eventDispatcher.dispatchEvent(new RoomWidgetAvatarInfoEvent(userId, userName, userData.type, userData.roomIndex, allowNameChange));
|
||||
}
|
||||
|
||||
private processUsableRoomObject(objectId: number): void
|
||||
@ -120,24 +136,24 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
{
|
||||
if(userData.ownerId === ownerId)
|
||||
{
|
||||
if(userData.hasSaddle && (specialType === FurniCategory._Str_6096)) replace = true;
|
||||
if(userData.hasSaddle && (specialType === FurniCategory.PET_SADDLE)) replace = true;
|
||||
|
||||
const figureParts = userData.figure.split(' ');
|
||||
const figurePart = (figureParts.length ? parseInt(figureParts[0]) : -1);
|
||||
|
||||
if(figurePart === part)
|
||||
{
|
||||
if(specialType === FurniCategory._Str_6915)
|
||||
if(specialType === FurniCategory.MONSTERPLANT_REVIVAL)
|
||||
{
|
||||
if(!userData.canRevive) continue;
|
||||
}
|
||||
|
||||
if(specialType === FurniCategory._Str_8726)
|
||||
if(specialType === FurniCategory.MONSTERPLANT_REBREED)
|
||||
{
|
||||
if((userData.petLevel < 7) || userData.canRevive || userData.canBreed) continue;
|
||||
}
|
||||
|
||||
if(specialType === FurniCategory._Str_9449)
|
||||
if(specialType === FurniCategory.MONSTERPLANT_FERTILIZE)
|
||||
{
|
||||
if((userData.petLevel >= 7) || userData.canRevive) continue;
|
||||
}
|
||||
@ -151,6 +167,11 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
if(useProductBubbles.length) this.container.eventDispatcher.dispatchEvent(new RoomWidgetUseProductBubbleEvent(RoomWidgetUseProductBubbleEvent.USE_PRODUCT_BUBBLES, useProductBubbles));
|
||||
}
|
||||
|
||||
private processRoomSessionPetStatusUpdateEvent(event: RoomSessionPetStatusUpdateEvent): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public get type(): string
|
||||
{
|
||||
return RoomWidgetEnum.AVATAR_INFO;
|
||||
@ -162,18 +183,29 @@ export class RoomWidgetAvatarInfoHandler extends RoomWidgetHandler
|
||||
RoomSessionUserDataUpdateEvent.USER_DATA_UPDATED,
|
||||
RoomSessionDanceEvent.RSDE_DANCE,
|
||||
RoomEngineUseProductEvent.USE_PRODUCT_FROM_INVENTORY,
|
||||
RoomEngineUseProductEvent.USE_PRODUCT_FROM_ROOM
|
||||
RoomEngineUseProductEvent.USE_PRODUCT_FROM_ROOM,
|
||||
RoomSessionPetStatusUpdateEvent.PET_STATUS_UPDATE
|
||||
];
|
||||
}
|
||||
|
||||
// UserNameUpdateEvent.UNUE_NAME_UPDATED
|
||||
// RoomSessionNestBreedingSuccessEvent.RSPFUE_NEST_BREEDING_SUCCESS
|
||||
// RoomSessionPetLevelUpdateEvent.RSPLUE_PET_LEVEL_UPDATE
|
||||
|
||||
public get messageTypes(): string[]
|
||||
{
|
||||
return [
|
||||
RoomWidgetRoomObjectMessage.GET_OWN_CHARACTER_INFO,
|
||||
RoomWidgetUserActionMessage.START_NAME_CHANGE,
|
||||
RoomWidgetUserActionMessage.REQUEST_PET_UPDATE,
|
||||
RoomWidgetUseProductMessage.PET_PRODUCT,
|
||||
RoomWidgetUserActionMessage.REQUEST_BREED_PET,
|
||||
RoomWidgetUserActionMessage.HARVEST_PET,
|
||||
RoomWidgetUserActionMessage.REVIVE_PET,
|
||||
RoomWidgetUserActionMessage.COMPOST_PLANT,
|
||||
RoomWidgetDanceMessage.DANCE,
|
||||
RoomWidgetAvatarExpressionMessage.AVATAR_EXPRESSION,
|
||||
RoomWidgetChangePostureMessage.CHANGE_POSTURE,
|
||||
RoomWidgetUseProductMessage.PET_PRODUCT
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { AvatarExpressionEnum, HabboClubLevelEnum, NitroEvent, RoomControllerLevel, RoomSessionChatEvent, RoomSettingsComposer, RoomWidgetEnum, RoomZoomEvent, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { AvatarExpressionEnum, HabboClubLevelEnum, NitroEvent, RoomControllerLevel, RoomRotatingEffect, RoomSessionChatEvent, RoomSettingsComposer, RoomShakingEffect, RoomWidgetEnum, RoomZoomEvent, TextureUtils } from '@nitrots/nitro-renderer';
|
||||
import { GetConfiguration, GetNitroInstance } from '../../..';
|
||||
import { GetRoomEngine, GetSessionDataManager } from '../../../..';
|
||||
import { GetRoomEngine, GetSessionDataManager, LocalizeText } from '../../../..';
|
||||
import { FloorplanEditorEvent } from '../../../../../events/floorplan-editor/FloorplanEditorEvent';
|
||||
import { dispatchUiEvent } from '../../../../../hooks';
|
||||
import { SendMessageHook } from '../../../../../hooks/messages';
|
||||
import { NotificationUtilities } from '../../../../../views/notification-center/common/NotificationUtilities';
|
||||
import { RoomWidgetFloodControlEvent, RoomWidgetUpdateEvent } from '../events';
|
||||
import { RoomWidgetChatMessage, RoomWidgetChatSelectAvatarMessage, RoomWidgetChatTypingMessage, RoomWidgetMessage, RoomWidgetRequestWidgetMessage } from '../messages';
|
||||
import { RoomWidgetHandler } from './RoomWidgetHandler';
|
||||
@ -65,6 +66,17 @@ export class RoomWidgetChatInputHandler extends RoomWidgetHandler
|
||||
|
||||
switch(firstPart.toLowerCase())
|
||||
{
|
||||
case ':shake':
|
||||
RoomShakingEffect.init(2500, 5000);
|
||||
RoomShakingEffect.turnVisualizationOn();
|
||||
|
||||
return null;
|
||||
|
||||
case ':rotate':
|
||||
RoomRotatingEffect.init(2500, 5000);
|
||||
RoomRotatingEffect.turnVisualizationOn();
|
||||
|
||||
return null;
|
||||
case ':d':
|
||||
case ';d':
|
||||
if(GetSessionDataManager().clubLevel === HabboClubLevelEnum.VIP)
|
||||
@ -109,6 +121,7 @@ export class RoomWidgetChatInputHandler extends RoomWidgetHandler
|
||||
|
||||
return null;
|
||||
case ':iddqd':
|
||||
case ':flip':
|
||||
GetRoomEngine().events.dispatchEvent(new RoomZoomEvent(this.container.roomSession.roomId, -1, true));
|
||||
|
||||
return null;
|
||||
@ -127,10 +140,11 @@ export class RoomWidgetChatInputHandler extends RoomWidgetHandler
|
||||
newWindow.document.write(image.outerHTML);
|
||||
return null;
|
||||
case ':pickall':
|
||||
// this.container.notificationService.alertWithConfirm('${room.confirm.pick_all}', '${generic.alert.title}', () =>
|
||||
// {
|
||||
// GetSessionDataManager().sendSpecialCommandMessage(':pickall');
|
||||
// });
|
||||
NotificationUtilities.confirm(LocalizeText('room.confirm.pick_all'), () =>
|
||||
{
|
||||
GetSessionDataManager().sendSpecialCommandMessage(':pickall');
|
||||
},
|
||||
null, null, null, LocalizeText('generic.alert.title'));
|
||||
|
||||
return null;
|
||||
case ':furni':
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespectComposer, PetSupplementComposer, PetType, RoomControllerLevel, RoomModerationSettings, RoomObjectCategory, RoomObjectOperationType, RoomObjectType, RoomObjectVariable, RoomSessionPetInfoUpdateEvent, RoomSessionUserBadgesEvent, RoomTradingLevelEnum, RoomUnitDropHandItemComposer, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUserData, RoomWidgetEnum, RoomWidgetEnumItemExtradataParameter, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespectComposer, PetSupplementComposer, PetType, RoomControllerLevel, RoomModerationSettings, RoomObjectCategory, RoomObjectOperationType, RoomObjectType, RoomObjectVariable, RoomSessionPetInfoUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, RoomTradingLevelEnum, RoomUnitDropHandItemComposer, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUserData, RoomWidgetEnum, RoomWidgetEnumItemExtradataParameter, Vector3d } from '@nitrots/nitro-renderer';
|
||||
import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
|
||||
import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events';
|
||||
import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent';
|
||||
@ -24,6 +24,9 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
|
||||
case RoomSessionUserBadgesEvent.RSUBE_BADGES:
|
||||
this.container.eventDispatcher.dispatchEvent(event);
|
||||
return;
|
||||
case RoomSessionUserFigureUpdateEvent.USER_FIGURE:
|
||||
this.processRoomSessionUserFigureUpdateEvent((event as RoomSessionUserFigureUpdateEvent));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -661,6 +664,17 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
|
||||
this.container.eventDispatcher.dispatchEvent(infostandEvent);
|
||||
}
|
||||
|
||||
private processRoomSessionUserFigureUpdateEvent(event: RoomSessionUserFigureUpdateEvent): void
|
||||
{
|
||||
const userData = this.container.roomSession.userDataManager.getUserDataByIndex(event.userId);
|
||||
|
||||
if(!userData) return;
|
||||
|
||||
// update active infostand figure
|
||||
// update motto
|
||||
// update activity points
|
||||
}
|
||||
|
||||
private checkGuildSetting(event: RoomWidgetUpdateInfostandUserEvent): boolean
|
||||
{
|
||||
if(event.isGuildRoom) return (event.roomControllerLevel >= RoomControllerLevel.GUILD_ADMIN);
|
||||
@ -766,7 +780,8 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
|
||||
{
|
||||
return [
|
||||
RoomSessionPetInfoUpdateEvent.PET_INFO,
|
||||
RoomSessionUserBadgesEvent.RSUBE_BADGES
|
||||
RoomSessionUserBadgesEvent.RSUBE_BADGES,
|
||||
RoomSessionUserFigureUpdateEvent.USER_FIGURE
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { IFurnitureData } from '@nitrots/nitro-renderer';
|
||||
import { GetSessionDataManager } from '.';
|
||||
import { ProductTypeEnum } from '../../../views/catalog/common/ProductTypeEnum';
|
||||
import { ProductTypeEnum } from '../../../components/catalog/common/ProductTypeEnum';
|
||||
|
||||
export function GetFurnitureData(furniClassId: number, productType: string): IFurnitureData
|
||||
{
|
||||
let furniData: IFurnitureData = null;
|
||||
|
||||
switch(productType.toUpperCase())
|
||||
switch(productType.toLowerCase())
|
||||
{
|
||||
case ProductTypeEnum.FLOOR:
|
||||
furniData = GetSessionDataManager().getFloorItemData(furniClassId);
|
||||
|
16
src/api/nitro/session/IsOwnerOfFloorFurniture.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { RoomObjectCategory, RoomObjectVariable } from '@nitrots/nitro-renderer';
|
||||
import { GetRoomSession } from '.';
|
||||
import { GetRoomEngine } from '..';
|
||||
import { GetSessionDataManager } from '../../../api';
|
||||
|
||||
export function IsOwnerOfFloorFurniture(id: number): boolean
|
||||
{
|
||||
const roomObject = GetRoomEngine().getRoomObject(GetRoomSession().roomId, id, RoomObjectCategory.FLOOR);
|
||||
|
||||
if(!roomObject || !roomObject.model) return false;
|
||||
|
||||
const userId = GetSessionDataManager().userId;
|
||||
const objectOwnerId = roomObject.model.getValue<number>(RoomObjectVariable.FURNITURE_OWNER_ID);
|
||||
|
||||
return (userId === objectOwnerId);
|
||||
}
|
@ -14,6 +14,7 @@ export * from './GetSessionDataManager';
|
||||
export * from './GoToDesktop';
|
||||
export * from './HasHabboClub';
|
||||
export * from './HasHabboVip';
|
||||
export * from './IsOwnerOfFloorFurniture';
|
||||
export * from './IsOwnerOfFurniture';
|
||||
export * from './IsRidingHorse';
|
||||
export * from './StartRoomSession';
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
BIN
src/assets/images/campaign/available.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/campaign/campaign_day_generic_bg.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/images/campaign/campaign_opened.png
Normal file
After Width: | Height: | Size: 744 B |
BIN
src/assets/images/campaign/locked.png
Normal file
After Width: | Height: | Size: 220 B |
BIN
src/assets/images/campaign/locked_bg.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/campaign/next.png
Normal file
After Width: | Height: | Size: 244 B |
BIN
src/assets/images/campaign/prev.png
Normal file
After Width: | Height: | Size: 235 B |
BIN
src/assets/images/campaign/unavailable.png
Normal file
After Width: | Height: | Size: 448 B |
BIN
src/assets/images/campaign/unlocked_bg.png
Normal file
After Width: | Height: | Size: 8.6 KiB |
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 |
@ -79,6 +79,11 @@
|
||||
font-weight: $font-weight-normal;
|
||||
color: $btn-link-color;
|
||||
text-decoration: $link-decoration;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:active {
|
||||
color: $btn-link-color !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $btn-link-hover-color;
|
||||
|
@ -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;
|
||||
|
||||
@ -732,7 +732,7 @@ $table-cell-padding-x-sm: .25rem !default;
|
||||
|
||||
$table-cell-vertical-align: top !default;
|
||||
|
||||
$table-color: $body-color !default;
|
||||
$table-color: $black !default;
|
||||
$table-bg: transparent !default;
|
||||
$table-accent-bg: transparent !default;
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
.form-check-input {
|
||||
width: $form-check-input-width;
|
||||
height: $form-check-input-width;
|
||||
margin-top: ($line-height-base - $form-check-input-width) * .5; // line-height minus check height
|
||||
//margin-top: ($line-height-base - $form-check-input-width) * .5; // line-height minus check height
|
||||
vertical-align: top;
|
||||
background-color: $form-check-input-bg;
|
||||
background-repeat: no-repeat;
|
||||
|
@ -1,4 +1,5 @@
|
||||
.fas {
|
||||
.fas,
|
||||
.svg-inline--fa {
|
||||
line-height: 0 !important;
|
||||
}
|
||||
|
||||
@ -303,6 +304,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;
|
||||
|
@ -1,9 +1,5 @@
|
||||
@import './fonts';
|
||||
@import './bootstrap/bootstrap';
|
||||
@import './fontawesome/fontawesome';
|
||||
@import './fontawesome/solid';
|
||||
@import './fontawesome/brands';
|
||||
@import './fontawesome/regular';
|
||||
@import '../node_modules/animate.css/animate.min.css';
|
||||
@import './scrollbars';
|
||||
@import './slider';
|
||||
|
@ -47,6 +47,10 @@ ul {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -78,3 +82,18 @@ ul {
|
||||
.z-index-1 {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.flex-basis-fit-content {
|
||||
flex-basis: fit-content;
|
||||
}
|
||||
|
||||
.flex-basis-max-content {
|
||||
flex-basis: max-content;
|
||||
}
|
||||
|
||||
.striped-children {
|
||||
|
||||
> :nth-child(1) {
|
||||
background-color: $table-striped-bg;
|
||||
}
|
||||
}
|
||||
|
28
src/common/AutoGrid.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { CSSProperties, FC, useMemo } from 'react';
|
||||
import { Grid, GridProps } from './Grid';
|
||||
|
||||
export interface AutoGridProps extends GridProps
|
||||
{
|
||||
columnMinWidth?: number;
|
||||
columnMinHeight?: number;
|
||||
}
|
||||
|
||||
export const AutoGrid: FC<AutoGridProps> = props =>
|
||||
{
|
||||
const { columnMinWidth = 40, columnMinHeight = 40, columnCount = 0, fullHeight = false, maxContent = true, overflow = 'auto', style = {}, ...rest } = props;
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
newStyle['--nitro-grid-column-min-height'] = (columnMinHeight + 'px');
|
||||
|
||||
if(columnCount > 1) newStyle.gridTemplateColumns = `repeat(auto-fill, minmax(${ columnMinWidth }px, 1fr))`;
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ columnMinWidth, columnMinHeight, columnCount, style ]);
|
||||
|
||||
return <Grid columnCount={ columnCount } fullHeight={ fullHeight } overflow={ overflow } style={ getStyle } { ...rest } />;
|
||||
}
|
70
src/common/Base.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { CSSProperties, DetailedHTMLProps, FC, HTMLAttributes, LegacyRef, useMemo } from 'react';
|
||||
import { ColorVariantType, FloatType, OverflowType, PositionType } from './types';
|
||||
|
||||
export interface BaseProps<T = HTMLElement> extends DetailedHTMLProps<HTMLAttributes<T>, T>
|
||||
{
|
||||
innerRef?: LegacyRef<T>;
|
||||
fit?: boolean;
|
||||
grow?: boolean;
|
||||
shrink?: boolean;
|
||||
fullWidth?: boolean;
|
||||
fullHeight?: boolean;
|
||||
overflow?: OverflowType;
|
||||
position?: PositionType;
|
||||
float?: FloatType;
|
||||
pointer?: boolean;
|
||||
textColor?: ColorVariantType;
|
||||
classNames?: string[];
|
||||
}
|
||||
|
||||
export const Base: FC<BaseProps<HTMLDivElement>> = props =>
|
||||
{
|
||||
const { ref = null, innerRef = null, fit = false, grow = false, shrink = false, fullWidth = false, fullHeight = false, overflow = null, position = null, float = null, pointer = false, textColor = null, classNames = [], className = '', style = {}, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [];
|
||||
|
||||
if(fit || fullWidth) newClassNames.push('w-100');
|
||||
|
||||
if(fit || fullHeight) newClassNames.push('h-100');
|
||||
|
||||
if(grow) newClassNames.push('flex-grow-1');
|
||||
|
||||
if(shrink) newClassNames.push('flex-shrink-0');
|
||||
|
||||
if(overflow) newClassNames.push('overflow-' + overflow);
|
||||
|
||||
if(position) newClassNames.push('position-' + position);
|
||||
|
||||
if(float) newClassNames.push('float-' + float);
|
||||
|
||||
if(pointer) newClassNames.push('cursor-pointer');
|
||||
|
||||
if(textColor) newClassNames.push('text-' + textColor);
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ fit, grow, shrink, fullWidth, fullHeight, overflow, position, float, pointer, textColor, classNames ]);
|
||||
|
||||
const getClassName = useMemo(() =>
|
||||
{
|
||||
let newClassName = getClassNames.join(' ');
|
||||
|
||||
if(className.length) newClassName += (' ' + className);
|
||||
|
||||
return newClassName.trim();
|
||||
}, [ getClassNames, className ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ style ]);
|
||||
|
||||
return <div ref={ innerRef } className={ getClassName } style={ getStyle } { ...rest } />;
|
||||
}
|
35
src/common/Button.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Flex, FlexProps } from './Flex';
|
||||
import { ButtonSizeType, ColorVariantType } from './types';
|
||||
|
||||
export interface ButtonProps extends FlexProps
|
||||
{
|
||||
variant?: ColorVariantType;
|
||||
size?: ButtonSizeType;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Button: FC<ButtonProps> = props =>
|
||||
{
|
||||
const { variant = 'primary', size = 'sm', active = false, disabled = false, classNames = [], ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'btn' ];
|
||||
|
||||
if(variant) newClassNames.push('btn-' + variant);
|
||||
|
||||
if(size) newClassNames.push('btn-' + size);
|
||||
|
||||
if(active) newClassNames.push('active');
|
||||
|
||||
if(disabled) newClassNames.push('disabled');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ variant, size, active, disabled, classNames ]);
|
||||
|
||||
return <Flex center classNames={ getClassNames } { ...rest } />;
|
||||
}
|
22
src/common/ButtonGroup.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from './Base';
|
||||
|
||||
export interface ButtonGroupProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
}
|
||||
|
||||
export const ButtonGroup: FC<ButtonGroupProps> = props =>
|
||||
{
|
||||
const { classNames = [], ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'btn-group' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return <Base classNames={ getClassNames } { ...rest } />;
|
||||
}
|
36
src/common/Column.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Flex, FlexProps } from './Flex';
|
||||
import { useGridContext } from './GridContext';
|
||||
import { ColumnSizesType } from './types';
|
||||
|
||||
export interface ColumnProps extends FlexProps
|
||||
{
|
||||
size?: ColumnSizesType;
|
||||
column?: boolean;
|
||||
}
|
||||
|
||||
export const Column: FC<ColumnProps> = props =>
|
||||
{
|
||||
const { size = 0, column = true, gap = 2, classNames = [], ...rest } = props;
|
||||
const { isCssGrid = false } = useGridContext();
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [];
|
||||
|
||||
if(size)
|
||||
{
|
||||
let colClassName = `col-${ size }`;
|
||||
|
||||
if(isCssGrid) colClassName = `g-${ colClassName }`;
|
||||
|
||||
newClassNames.push(colClassName);
|
||||
}
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ size, isCssGrid, classNames ]);
|
||||
|
||||
return <Flex classNames={ getClassNames } column={ column } gap={ gap } { ...rest } />;
|
||||
}
|
54
src/common/Flex.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from './Base';
|
||||
import { AlignItemType, AlignSelfType, JustifyContentType, SpacingType } from './types';
|
||||
|
||||
export interface FlexProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
inline?: boolean;
|
||||
column?: boolean;
|
||||
reverse?: boolean;
|
||||
gap?: SpacingType;
|
||||
center?: boolean;
|
||||
alignSelf?: AlignSelfType;
|
||||
alignItems?: AlignItemType;
|
||||
justifyContent?: JustifyContentType;
|
||||
}
|
||||
|
||||
export const Flex: FC<FlexProps> = props =>
|
||||
{
|
||||
const { inline = false, column = undefined, reverse = false, gap = null, center = false, alignSelf = null, alignItems = null, justifyContent = null, classNames = [], ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [];
|
||||
|
||||
if(inline) newClassNames.push('d-inline-flex');
|
||||
else newClassNames.push('d-flex');
|
||||
|
||||
if(column)
|
||||
{
|
||||
if(reverse) newClassNames.push('flex-column-reverse');
|
||||
else newClassNames.push('flex-column');
|
||||
}
|
||||
else
|
||||
{
|
||||
if(reverse) newClassNames.push('flex-row-reverse');
|
||||
}
|
||||
|
||||
if(gap) newClassNames.push('gap-' + gap);
|
||||
|
||||
if(alignSelf) newClassNames.push('align-self-' + alignSelf);
|
||||
|
||||
if(alignItems) newClassNames.push('align-items-' + alignItems);
|
||||
|
||||
if(justifyContent) newClassNames.push('justify-content-' + justifyContent);
|
||||
|
||||
if(!alignItems && !justifyContent && center) newClassNames.push('align-items-center', 'justify-content-center');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ inline, column, reverse, gap, center, alignSelf, alignItems, justifyContent, classNames ]);
|
||||
|
||||
return <Base classNames={ getClassNames } { ...rest } />;
|
||||
}
|
22
src/common/FormGroup.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Flex, FlexProps } from './Flex';
|
||||
|
||||
export interface FormGroupProps extends FlexProps
|
||||
{
|
||||
}
|
||||
|
||||
export const FormGroup: FC<FormGroupProps> = props =>
|
||||
{
|
||||
const { classNames = [], ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'form-group' ];
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ classNames ]);
|
||||
|
||||
return <Flex classNames={ getClassNames } { ...rest } />;
|
||||
}
|
64
src/common/Grid.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { CSSProperties } from 'styled-components';
|
||||
import { Base, BaseProps } from './Base';
|
||||
import { GridContextProvider } from './GridContext';
|
||||
import { AlignItemType, AlignSelfType, JustifyContentType, SpacingType } from './types';
|
||||
|
||||
export interface GridProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
inline?: boolean;
|
||||
gap?: SpacingType;
|
||||
maxContent?: boolean;
|
||||
columnCount?: number;
|
||||
center?: boolean;
|
||||
alignSelf?: AlignSelfType;
|
||||
alignItems?: AlignItemType;
|
||||
justifyContent?: JustifyContentType;
|
||||
}
|
||||
|
||||
export const Grid: FC<GridProps> = props =>
|
||||
{
|
||||
const { inline = false, gap = 2, maxContent = false, columnCount = 0, center = false, alignSelf = null, alignItems = null, justifyContent = null, fullHeight = true, classNames = [], style = {}, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [];
|
||||
|
||||
if(inline) newClassNames.push('inline-grid');
|
||||
else newClassNames.push('grid');
|
||||
|
||||
if(gap) newClassNames.push('gap-' + gap);
|
||||
else if(gap === 0) newClassNames.push('gap-0');
|
||||
|
||||
if(maxContent) newClassNames.push('flex-basis-max-content');
|
||||
|
||||
if(alignSelf) newClassNames.push('align-self-' + alignSelf);
|
||||
|
||||
if(alignItems) newClassNames.push('align-items-' + alignItems);
|
||||
|
||||
if(justifyContent) newClassNames.push('justify-content-' + justifyContent);
|
||||
|
||||
if(!alignItems && !justifyContent && center) newClassNames.push('align-items-center', 'justify-content-center');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ inline, gap, maxContent, alignSelf, alignItems, justifyContent, center, classNames ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle: CSSProperties = {};
|
||||
|
||||
if(columnCount) newStyle['--bs-columns'] = columnCount.toString();
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ columnCount, style ]);
|
||||
|
||||
return (
|
||||
<GridContextProvider value={ { isCssGrid: true } }>
|
||||
<Base fullHeight={ fullHeight } classNames={ getClassNames } style={ getStyle } { ...rest } />
|
||||
</GridContextProvider>
|
||||
);
|
||||
}
|
17
src/common/GridContext.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { createContext, FC, ProviderProps, useContext } from 'react';
|
||||
|
||||
export interface IGridContext
|
||||
{
|
||||
isCssGrid: boolean;
|
||||
}
|
||||
|
||||
const GridContext = createContext<IGridContext>({
|
||||
isCssGrid: false
|
||||
});
|
||||
|
||||
export const GridContextProvider: FC<ProviderProps<IGridContext>> = props =>
|
||||
{
|
||||
return <GridContext.Provider value={ props.value }>{ props.children }</GridContext.Provider>
|
||||
}
|
||||
|
||||
export const useGridContext = () => useContext(GridContext);
|
63
src/common/Text.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from './Base';
|
||||
import { ColorVariantType, FontSizeType, FontWeightType, TextAlignType } from './types';
|
||||
|
||||
export interface TextProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
variant?: ColorVariantType;
|
||||
fontWeight?: FontWeightType;
|
||||
fontSize?: FontSizeType;
|
||||
align?: TextAlignType;
|
||||
bold?: boolean;
|
||||
underline?: boolean;
|
||||
italics?: boolean;
|
||||
truncate?: boolean;
|
||||
center?: boolean;
|
||||
textEnd?: boolean;
|
||||
small?: boolean;
|
||||
wrap?: boolean;
|
||||
noWrap?: boolean;
|
||||
textBreak?: boolean;
|
||||
}
|
||||
|
||||
export const Text: FC<TextProps> = props =>
|
||||
{
|
||||
const { variant = 'black', fontWeight = null, fontSize = 0, align = null, bold = false, underline = false, italics = false, truncate = false, center = false, textEnd = false, small = false, wrap = false, noWrap = false, textBreak = false, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'd-inline' ];
|
||||
|
||||
if(variant) newClassNames.push('text-' + variant);
|
||||
|
||||
if(bold) newClassNames.push('fw-bold');
|
||||
|
||||
if(fontWeight) newClassNames.push('fw-' + fontWeight);
|
||||
|
||||
if(fontSize) newClassNames.push('fs-' + fontSize);
|
||||
|
||||
if(align) newClassNames.push('text-' + align);
|
||||
|
||||
if(underline) newClassNames.push('text-decoration-underline');
|
||||
|
||||
if(italics) newClassNames.push('fst-italic');
|
||||
|
||||
if(truncate) newClassNames.push('text-truncate');
|
||||
|
||||
if(center) newClassNames.push('text-center');
|
||||
|
||||
if(textEnd) newClassNames.push('text-end');
|
||||
|
||||
if(small) newClassNames.push('small');
|
||||
|
||||
if(wrap) newClassNames.push('text-wrap');
|
||||
|
||||
if(noWrap) newClassNames.push('text-nowrap');
|
||||
|
||||
if(textBreak) newClassNames.push('text-break');
|
||||
|
||||
return newClassNames;
|
||||
}, [ variant, fontWeight, fontSize, align, bold, underline, italics, truncate, center, textEnd, small, wrap, noWrap, textBreak ]);
|
||||
|
||||
return <Base classNames={ getClassNames } { ...rest } />;
|
||||
}
|
43
src/common/index.scss
Normal file
@ -0,0 +1,43 @@
|
||||
.layout-grid-item {
|
||||
height: var(--nitro-grid-column-min-height, 45px);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: $grid-bg-color;
|
||||
|
||||
&.active {
|
||||
border-color: $grid-active-border-color !important;
|
||||
background-color: $grid-active-bg-color;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
img {
|
||||
opacity: .5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.unseen {
|
||||
background-color: rgba($success, 0.4);
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
background-position-y: -35px;
|
||||
}
|
||||
|
||||
&.has-highlight {
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
background-color: $white;
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
}
|
13
src/common/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export * from './AutoGrid';
|
||||
export * from './Base';
|
||||
export * from './Button';
|
||||
export * from './ButtonGroup';
|
||||
export * from './Column';
|
||||
export * from './Flex';
|
||||
export * from './FormGroup';
|
||||
export * from './Grid';
|
||||
export * from './GridContext';
|
||||
export * from './layout';
|
||||
export * from './Text';
|
||||
export * from './types';
|
||||
export * from './utils';
|
75
src/common/layout/LayoutGridItem.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { ItemCountView } from '../../views/shared/item-count/ItemCountView';
|
||||
import { LimitedEditionStyledNumberView } from '../../views/shared/limited-edition/LimitedEditionStyledNumberView';
|
||||
import { Base } from '../Base';
|
||||
import { Column, ColumnProps } from '../Column';
|
||||
|
||||
export interface LayoutGridItemProps extends ColumnProps
|
||||
{
|
||||
itemImage?: string;
|
||||
itemColor?: string;
|
||||
itemActive?: boolean;
|
||||
itemCount?: number;
|
||||
itemCountMinimum?: number;
|
||||
itemUniqueSoldout?: boolean;
|
||||
itemUniqueNumber?: number;
|
||||
itemUnseen?: boolean;
|
||||
itemHighlight?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const LayoutGridItem: FC<LayoutGridItemProps> = props =>
|
||||
{
|
||||
const { itemImage = undefined, itemColor = undefined, itemActive = false, itemCount = 1, itemCountMinimum = 1, itemUniqueSoldout = false, itemUniqueNumber = -2, itemUnseen = false, itemHighlight = false, disabled = false, center = true, column = true, style = {}, classNames = [], position = 'relative', overflow = 'hidden', children = null, ...rest } = props;
|
||||
|
||||
const getClassNames = useMemo(() =>
|
||||
{
|
||||
const newClassNames: string[] = [ 'layout-grid-item', 'border', 'border-2', 'border-muted', 'rounded' ];
|
||||
|
||||
if(itemActive) newClassNames.push('active');
|
||||
|
||||
if(itemUniqueSoldout || (itemUniqueNumber > 0)) newClassNames.push('unique-item');
|
||||
|
||||
if(itemUniqueSoldout) newClassNames.push('sold-out');
|
||||
|
||||
if(itemUnseen) newClassNames.push('unseen');
|
||||
|
||||
if(itemHighlight) newClassNames.push('has-highlight');
|
||||
|
||||
if(disabled) newClassNames.push('disabled')
|
||||
|
||||
if(itemImage === null) newClassNames.push('icon', 'loading-icon');
|
||||
|
||||
if(classNames.length) newClassNames.push(...classNames);
|
||||
|
||||
return newClassNames;
|
||||
}, [ itemActive, itemUniqueSoldout, itemUniqueNumber, itemUnseen, itemHighlight, disabled, itemImage, classNames ]);
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
let newStyle = { ...style };
|
||||
|
||||
if(itemImage) newStyle.backgroundImage = `url(${ itemImage })`;
|
||||
|
||||
if(itemColor) newStyle.backgroundColor = itemColor;
|
||||
|
||||
if(Object.keys(style).length) newStyle = { ...newStyle, ...style };
|
||||
|
||||
return newStyle;
|
||||
}, [ style, itemImage, itemColor ]);
|
||||
|
||||
return (
|
||||
<Column center={ center } pointer position={ position } overflow={ overflow } column={ column } classNames={ getClassNames } style={ getStyle } { ...rest }>
|
||||
{ (itemCount > itemCountMinimum) &&
|
||||
<ItemCountView count={ itemCount } /> }
|
||||
{ (itemUniqueNumber > 0) &&
|
||||
<>
|
||||
<Base fit className="unique-bg-override" style={ { backgroundImage: `url(${ itemImage })` } } />
|
||||
<div className="position-absolute bottom-0 unique-item-counter">
|
||||
<LimitedEditionStyledNumberView value={ itemUniqueNumber } />
|
||||
</div>
|
||||
</> }
|
||||
{ children }
|
||||
</Column>
|
||||
);
|
||||
}
|
23
src/common/layout/LayoutImage.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { FC, useMemo } from 'react';
|
||||
import { Base, BaseProps } from '../Base';
|
||||
|
||||
export interface LayoutImageProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
export const LayoutImage: FC<LayoutImageProps> = props =>
|
||||
{
|
||||
const { imageUrl = null, fit = true, style = null, ...rest } = props;
|
||||
|
||||
const getStyle = useMemo(() =>
|
||||
{
|
||||
const newStyle = { ...style };
|
||||
|
||||
if(imageUrl) newStyle.background = `url(${ imageUrl }) center no-repeat`;
|
||||
|
||||
return newStyle;
|
||||
}, [ style, imageUrl ]);
|
||||
|
||||
return <Base fit={ fit } style={ getStyle } { ...rest } />;
|
||||
}
|
2
src/common/layout/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './LayoutGridItem';
|
||||
export * from './LayoutImage';
|
1
src/common/types/AlignItemType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type AlignItemType = 'start' | 'end' | 'center' | 'baseline' | 'stretch';
|
1
src/common/types/AlignSelfType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type AlignSelfType = 'start' | 'end' | 'center' | 'baseline' | 'stretch';
|
1
src/common/types/ButtonSizeType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type ButtonSizeType = 'lg' | 'sm';
|
1
src/common/types/ColorVariantType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type ColorVariantType = 'primary' | 'success' | 'danger' | 'secondary' | 'link' | 'black' | 'white' | 'dark' | 'warning' | 'muted';
|
1
src/common/types/ColumnSizesType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type ColumnSizesType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
1
src/common/types/FloatType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type FloatType = 'start' | 'end' | 'none';
|
1
src/common/types/FontSizeType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type FontSizeType = 1 | 2 | 3 | 4 | 5 | 6;
|
1
src/common/types/FontWeightType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type FontWeightType = 'bold' | 'bolder' | 'normal' | 'light' | 'lighter';
|
1
src/common/types/JustifyContentType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type JustifyContentType = 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly';
|
1
src/common/types/OverflowType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type OverflowType = 'hidden' | 'auto' | 'unset';
|
1
src/common/types/PositionType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type PositionType = 'static' | 'relative' | 'fixed' | 'absolute' | 'sticky';
|
1
src/common/types/SpacingType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type SpacingType = 0 | 1 | 2 | 3 | 4 | 5;
|
1
src/common/types/TextAlignType.ts
Normal file
@ -0,0 +1 @@
|
||||
export type TextAlignType = 'start' | 'center' | 'end';
|
13
src/common/types/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export * from './AlignItemType';
|
||||
export * from './AlignSelfType';
|
||||
export * from './ButtonSizeType';
|
||||
export * from './ColorVariantType';
|
||||
export * from './ColumnSizesType';
|
||||
export * from './FloatType';
|
||||
export * from './FontSizeType';
|
||||
export * from './FontWeightType';
|
||||
export * from './JustifyContentType';
|
||||
export * from './OverflowType';
|
||||
export * from './PositionType';
|
||||
export * from './SpacingType';
|
||||
export * from './TextAlignType';
|
14
src/common/utils/CreateTransitionToIcon.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NitroToolbarAnimateIconEvent } from '@nitrots/nitro-renderer';
|
||||
import { GetRoomEngine } from '../../api';
|
||||
|
||||
export const CreateTransitionToIcon = (image: HTMLImageElement, fromElement: HTMLElement, icon: string) =>
|
||||
{
|
||||
const bounds = fromElement.getBoundingClientRect();
|
||||
const x = (bounds.x + (bounds.width / 2));
|
||||
const y = (bounds.y + (bounds.height / 2));
|
||||
const event = new NitroToolbarAnimateIconEvent(image, x, y);
|
||||
|
||||
event.iconName = icon;
|
||||
|
||||
GetRoomEngine().events.dispatchEvent(event);
|
||||
}
|
1
src/common/utils/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './CreateTransitionToIcon';
|
@ -3,19 +3,6 @@
|
||||
height: $achievement-height;
|
||||
}
|
||||
|
||||
.nitro-achievements-category-grid {
|
||||
--nitro-grid-column-min-width: 80px !important;
|
||||
|
||||
.grid-item {
|
||||
height: 80px;
|
||||
max-height: 80px;
|
||||
|
||||
.achievement-score {
|
||||
top: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-achievements-back-arrow {
|
||||
background: url('../../assets/images/achievements/back-arrow.png') no-repeat center;
|
||||
width: 33px;
|
||||
@ -23,6 +10,6 @@
|
||||
}
|
||||
|
||||
.nitro-achievements-badge-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
}
|
@ -1,18 +1,21 @@
|
||||
import { AchievementData, AchievementEvent, AchievementsEvent, AchievementsScoreEvent, RequestAchievementsMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { GetConfiguration, LocalizeText } from '../../api';
|
||||
import { Base } from '../../common/Base';
|
||||
import { Column } from '../../common/Column';
|
||||
import { Flex } from '../../common/Flex';
|
||||
import { Text } from '../../common/Text';
|
||||
import { AchievementsUIEvent, AchievementsUIUnseenCountEvent } from '../../events/achievements';
|
||||
import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../hooks';
|
||||
import { useUiEvent } from '../../hooks/events';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView, NitroLayoutFlexColumn, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardSubHeaderView, NitroCardView } from '../../layout';
|
||||
import { NitroLayoutBase } from '../../layout/base';
|
||||
import { AchievementsViewProps } from './AchievementsView.types';
|
||||
import { AchievementCategory } from './common/AchievementCategory';
|
||||
import { AchievementUtilities } from './common/AchievementUtilities';
|
||||
import { AchievementsCategoryListView } from './views/category-list/AchievementsCategoryListView';
|
||||
import { AchievementCategoryView } from './views/category/AchievementCategoryView';
|
||||
|
||||
export const AchievementsView: FC<AchievementsViewProps> = props =>
|
||||
export const AchievementsView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ isInitalized, setIsInitalized ] = useState(false);
|
||||
@ -170,6 +173,15 @@ export const AchievementsView: FC<AchievementsViewProps> = props =>
|
||||
return achievementCategories.find(existing => (existing.code === selectedCategoryCode));
|
||||
}, [ achievementCategories, selectedCategoryCode ]);
|
||||
|
||||
const getCategoryIcon = useMemo(() =>
|
||||
{
|
||||
if(!getSelectedCategory) return null;
|
||||
|
||||
const imageUrl = GetConfiguration<string>('achievements.images.url');
|
||||
|
||||
return imageUrl.replace('%image%', `achicon_${ getSelectedCategory.code }`);
|
||||
}, [ getSelectedCategory ]);
|
||||
|
||||
const setAchievementSeen = useCallback((code: string, achievementId: number) =>
|
||||
{
|
||||
const newCategories = [ ...achievementCategories ];
|
||||
@ -207,38 +219,27 @@ export const AchievementsView: FC<AchievementsViewProps> = props =>
|
||||
<NitroCardView uniqueKey="achievements" className="nitro-achievements" simple={ true }>
|
||||
<NitroCardHeaderView headerText={ LocalizeText('inventory.achievements') } onCloseClick={ event => setIsVisible(false) } />
|
||||
{ getSelectedCategory &&
|
||||
<NitroCardSubHeaderView className="justify-content-center align-items-center cursor-pointer" gap={ 3 }>
|
||||
<NitroCardSubHeaderView position="relative" className="justify-content-center align-items-center cursor-pointer" gap={ 3 }>
|
||||
<NitroLayoutBase onClick={ event => setSelectedCategoryCode(null) } className="nitro-achievements-back-arrow" />
|
||||
<NitroLayoutFlexColumn className="flex-grow-1">
|
||||
<NitroLayoutBase className="fs-4 text-black fw-bold">
|
||||
{ LocalizeText(`quests.${ getSelectedCategory.code }.name`) }
|
||||
</NitroLayoutBase>
|
||||
<NitroLayoutBase className="text-black">
|
||||
{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ getSelectedCategory.getProgress().toString(), getSelectedCategory.getMaxProgress().toString() ]) }
|
||||
</NitroLayoutBase>
|
||||
</NitroLayoutFlexColumn>
|
||||
<Column grow gap={ 0 }>
|
||||
<Text fontSize={ 4 } fontWeight="bold" className="text-small">{ LocalizeText(`quests.${ getSelectedCategory.code }.name`) }</Text>
|
||||
<Text>{ LocalizeText('achievements.details.categoryprogress', [ 'progress', 'limit' ], [ getSelectedCategory.getProgress().toString(), getSelectedCategory.getMaxProgress().toString() ]) }</Text>
|
||||
</Column>
|
||||
</NitroCardSubHeaderView> }
|
||||
<NitroCardContentView>
|
||||
<NitroLayoutGrid>
|
||||
<NitroLayoutGridColumn size={ 12 }>
|
||||
{ !getSelectedCategory &&
|
||||
<>
|
||||
<AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />
|
||||
<NitroLayoutFlexColumn className="flex-grow-1 justify-content-end" gap={ 2 }>
|
||||
<NitroLayoutBase className="progress">
|
||||
<NitroLayoutBase className="progress-bar" style={ { width: (scaledProgressPercent + '%') }}>
|
||||
{ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) }
|
||||
</NitroLayoutBase>
|
||||
</NitroLayoutBase>
|
||||
<NitroLayoutBase className="bg-muted text-black text-center rounded">
|
||||
{ LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) }
|
||||
</NitroLayoutBase>
|
||||
</NitroLayoutFlexColumn>
|
||||
</> }
|
||||
{ getSelectedCategory &&
|
||||
<AchievementCategoryView category={ getSelectedCategory } setAchievementSeen={ setAchievementSeen } /> }
|
||||
</NitroLayoutGridColumn>
|
||||
</NitroLayoutGrid>
|
||||
{ !getSelectedCategory &&
|
||||
<>
|
||||
<AchievementsCategoryListView categories={ achievementCategories } selectedCategoryCode={ selectedCategoryCode } setSelectedCategoryCode={ setSelectedCategoryCode } />
|
||||
<Column grow justifyContent="end">
|
||||
<Base className="progress" position="relative">
|
||||
<Flex fit center position="absolute" className="text-black">{ LocalizeText('achievements.categories.totalprogress', [ 'progress', 'limit' ], [ getProgress.toString(), getMaxProgress.toString() ]) }</Flex>
|
||||
<Base className="progress-bar bg-success" style={ { width: (scaledProgressPercent + '%') }} />
|
||||
</Base>
|
||||
<Text className="bg-muted rounded p-1" center>{ LocalizeText('achievements.categories.score', [ 'score' ], [ achievementScore.toString() ]) }</Text>
|
||||
</Column>
|
||||
</> }
|
||||
{ getSelectedCategory &&
|
||||
<AchievementCategoryView category={ getSelectedCategory } setAchievementSeen={ setAchievementSeen } /> }
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|
@ -1,7 +1,14 @@
|
||||
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
|
||||
import { BaseProps } from '../../../../common/Base';
|
||||
import { BadgeImageView } from '../../../../views/shared/badge-image/BadgeImageView';
|
||||
import { AchievementUtilities } from '../../common/AchievementUtilities';
|
||||
import { AchievementBadgeViewProps } from './AchievementBadgeView.types';
|
||||
|
||||
export interface AchievementBadgeViewProps extends BaseProps<HTMLDivElement>
|
||||
{
|
||||
achievement: AchievementData;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export const AchievementBadgeView: FC<AchievementBadgeViewProps> = props =>
|
||||
{
|
@ -0,0 +1,66 @@
|
||||
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { LocalizeBadgeDescription, LocalizeBadgeName, LocalizeText } from '../../../../api';
|
||||
import { Base } from '../../../../common/Base';
|
||||
import { Column } from '../../../../common/Column';
|
||||
import { Flex } from '../../../../common/Flex';
|
||||
import { Text } from '../../../../common/Text';
|
||||
import { CurrencyIcon } from '../../../../views/shared/currency-icon/CurrencyIcon';
|
||||
import { AchievementUtilities } from '../../common/AchievementUtilities';
|
||||
import { GetAchievementLevel } from '../../common/GetAchievementLevel';
|
||||
import { GetScaledProgressPercent } from '../../common/GetScaledProgressPercent';
|
||||
import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView';
|
||||
|
||||
export interface AchievementDetailsViewProps
|
||||
{
|
||||
achievement: AchievementData;
|
||||
}
|
||||
|
||||
export const AchievementDetailsView: FC<AchievementDetailsViewProps> = props =>
|
||||
{
|
||||
const { achievement = null } = props;
|
||||
|
||||
if(!achievement) return null;
|
||||
|
||||
const achievementLevel = GetAchievementLevel(achievement);
|
||||
const scaledProgressPercent = GetScaledProgressPercent(achievement);
|
||||
|
||||
return (
|
||||
<Flex shrink className="bg-muted rounded p-2 text-black" gap={ 2 } overflow="hidden">
|
||||
<Column center>
|
||||
<AchievementBadgeView className="nitro-achievements-badge-image" achievement={ achievement } scale={ 2 } />
|
||||
<Text fontWeight="bold">
|
||||
{ LocalizeText('achievements.details.level', [ 'level', 'limit' ], [ achievementLevel.toString(), achievement.levelCount.toString() ]) }
|
||||
</Text>
|
||||
</Column>
|
||||
<Column fullWidth justifyContent="center" overflow="hidden">
|
||||
<Column gap={ 1 }>
|
||||
<Text fontWeight="bold" truncate>
|
||||
{ LocalizeBadgeName(AchievementUtilities.getBadgeCode(achievement)) }
|
||||
</Text>
|
||||
<Text truncate>
|
||||
{ LocalizeBadgeDescription(AchievementUtilities.getBadgeCode(achievement)) }
|
||||
</Text>
|
||||
</Column>
|
||||
{ ((achievement.levelRewardPoints > 0) || (achievement.scoreLimit > 0)) &&
|
||||
<Column gap={ 1 }>
|
||||
{ (achievement.levelRewardPoints > 0) &&
|
||||
<Flex alignItems="center" gap={ 1 }>
|
||||
<Text truncate className="small">
|
||||
{ LocalizeText('achievements.details.reward') }
|
||||
</Text>
|
||||
<Flex center className="fw-bold small" gap={ 1 }>
|
||||
{ achievement.levelRewardPoints }
|
||||
<CurrencyIcon type={ achievement.levelRewardPointType } />
|
||||
</Flex>
|
||||
</Flex> }
|
||||
{ (achievement.scoreLimit > 0) &&
|
||||
<Base className="progress" position="relative">
|
||||
<Flex fit center position="absolute" className="text-black"> { LocalizeText('achievements.details.progress', [ 'progress', 'limit' ], [ (achievement.currentPoints + achievement.scoreAtStartOfLevel).toString(), (achievement.scoreLimit + achievement.scoreAtStartOfLevel).toString() ]) }</Flex>
|
||||
<Base className="progress-bar" style={ { width: (scaledProgressPercent + '%') }} />
|
||||
</Base> }
|
||||
</Column> }
|
||||
</Column>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||
import { FC } from 'react';
|
||||
import { LayoutGridItem, LayoutGridItemProps } from '../../../../common/layout/LayoutGridItem';
|
||||
import { AchievementBadgeView } from '../achievement-badge/AchievementBadgeView';
|
||||
|
||||
export interface AchievementListItemViewProps extends LayoutGridItemProps
|
||||
{
|
||||
achievement: AchievementData;
|
||||
}
|
||||
|
||||
export const AchievementListItemView: FC<AchievementListItemViewProps> = props =>
|
||||
{
|
||||
const { achievement = null, children = null, ...rest } = props;
|
||||
|
||||
if(!achievement) return null;
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemCount={ achievement.unseen } itemCountMinimum={ 0 } { ...rest }>
|
||||
<AchievementBadgeView achievement={ achievement } />
|
||||
{ children }
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { AchievementData } from '@nitrots/nitro-renderer';
|
||||
import { Dispatch, FC, SetStateAction } from 'react';
|
||||
import { AutoGrid } from '../../../../common/AutoGrid';
|
||||
import { AchievementListItemView } from './AchievementListItemView';
|
||||
|
||||
export interface AchievementListViewProps
|
||||
{
|
||||
achievements: AchievementData[];
|
||||
selectedAchievementId: number;
|
||||
setSelectedAchievementId: Dispatch<SetStateAction<number>>;
|
||||
}
|
||||
|
||||
export const AchievementListView: FC<AchievementListViewProps> = props =>
|
||||
{
|
||||
const { achievements = null, selectedAchievementId = 0, setSelectedAchievementId = null, children = null } = props;
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ 6 } columnMinWidth={ 50 } columnMinHeight={ 50 }>
|
||||
{ achievements && (achievements.length > 0) && achievements.map((achievement, index) =>
|
||||
{
|
||||
return <AchievementListItemView key={ index } achievement={ achievement } itemActive={ (selectedAchievementId === achievement.achievementId) } onClick={ event => setSelectedAchievementId(achievement.achievementId) } />;
|
||||
}) }
|
||||
{ children }
|
||||
</AutoGrid>
|
||||
);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import { FC, useCallback, useMemo } from 'react';
|
||||
import { GetConfiguration, LocalizeText } from '../../../../api';
|
||||
import { LayoutGridItem, LayoutGridItemProps } from '../../../../common/layout/LayoutGridItem';
|
||||
import { LayoutImage } from '../../../../common/layout/LayoutImage';
|
||||
import { Text } from '../../../../common/Text';
|
||||
import { AchievementCategory } from '../../common/AchievementCategory';
|
||||
|
||||
export interface AchievementCategoryListItemViewProps extends LayoutGridItemProps
|
||||
{
|
||||
category: AchievementCategory;
|
||||
}
|
||||
|
||||
export const AchievementsCategoryListItemView: FC<AchievementCategoryListItemViewProps> = props =>
|
||||
{
|
||||
const { category = null, ...rest } = props;
|
||||
|
||||
const progress = category.getProgress();
|
||||
const maxProgress = category.getMaxProgress();
|
||||
|
||||
const getCategoryImage = useMemo(() =>
|
||||
{
|
||||
const imageUrl = GetConfiguration<string>('achievements.images.url');
|
||||
|
||||
return imageUrl.replace('%image%', `achcategory_${ category.code }_${ ((progress > 0) ? 'active' : 'inactive') }`);
|
||||
}, [ category, progress ]);
|
||||
|
||||
const getTotalUnseen = useCallback(() =>
|
||||
{
|
||||
let unseen = 0;
|
||||
|
||||
for(const achievement of category.achievements) unseen += achievement.unseen;
|
||||
|
||||
return unseen;
|
||||
}, [ category ]);
|
||||
|
||||
return (
|
||||
<LayoutGridItem itemCount={ getTotalUnseen() } itemCountMinimum={ 0 } gap={ 1 } { ...rest }>
|
||||
<Text fullWidth center className="small pt-1">{ LocalizeText(`quests.${ category.code }.name`) }</Text>
|
||||
<LayoutImage position="relative" imageUrl={ getCategoryImage }>
|
||||
<Text fullWidth center position="absolute" variant="white" style={ { fontSize: 12, bottom: 9 } }>{ progress } / { maxProgress }</Text>
|
||||
</LayoutImage>
|
||||
</LayoutGridItem>
|
||||
);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { Dispatch, FC, SetStateAction } from 'react';
|
||||
import { AutoGrid } from '../../../../common/AutoGrid';
|
||||
import { AchievementCategory } from '../../common/AchievementCategory';
|
||||
import { AchievementsCategoryListItemView } from './AchievementsCategoryListItemView';
|
||||
|
||||
export interface AchievementsCategoryListViewProps
|
||||
{
|
||||
categories: AchievementCategory[];
|
||||
selectedCategoryCode: string;
|
||||
setSelectedCategoryCode: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const AchievementsCategoryListView: FC<AchievementsCategoryListViewProps> = props =>
|
||||
{
|
||||
const { categories = null, selectedCategoryCode = null, setSelectedCategoryCode = null, children = null } = props;
|
||||
|
||||
return (
|
||||
<AutoGrid columnCount={ 3 } columnMinWidth={ 90 } columnMinHeight={ 100 }>
|
||||
{ categories && (categories.length > 0) && categories.map((category, index) => <AchievementsCategoryListItemView key={ index } category={ category } itemActive={ (selectedCategoryCode === category.code) } onClick={ event => setSelectedCategoryCode(category.code) } /> ) }
|
||||
{ children }
|
||||
</AutoGrid>
|
||||
);
|
||||
};
|
@ -1,8 +1,14 @@
|
||||
import { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { NitroLayoutFlexColumn } from '../../../../layout';
|
||||
import { Column } from '../../../../common/Column';
|
||||
import { AchievementCategory } from '../../common/AchievementCategory';
|
||||
import { AchievementDetailsView } from '../achievement-details/AchievementDetailsView';
|
||||
import { AchievementListView } from '../achievement-list/AchievementListView';
|
||||
import { AchievementCategoryViewProps } from './AchievementCategoryView.types';
|
||||
|
||||
export class AchievementCategoryViewProps
|
||||
{
|
||||
category: AchievementCategory;
|
||||
setAchievementSeen: (code: string, achievementId: number) => void;
|
||||
}
|
||||
|
||||
export const AchievementCategoryView: FC<AchievementCategoryViewProps> = props =>
|
||||
{
|
||||
@ -42,10 +48,10 @@ export const AchievementCategoryView: FC<AchievementCategoryViewProps> = props =
|
||||
if(!category) return null;
|
||||
|
||||
return (
|
||||
<NitroLayoutFlexColumn className="justify-content-between h-100" gap={ 2 }>
|
||||
<Column fullHeight justifyContent="between">
|
||||
<AchievementListView achievements={ category.achievements } selectedAchievementId={ selectedAchievementId } setSelectedAchievementId={ setSelectedAchievementId } />
|
||||
{ getSelectedAchievement &&
|
||||
<AchievementDetailsView achievement={ getSelectedAchievement } /> }
|
||||
</NitroLayoutFlexColumn>
|
||||
</Column>
|
||||
);
|
||||
}
|
@ -192,7 +192,7 @@
|
||||
}
|
||||
|
||||
|
||||
&.spotlight {
|
||||
&.spotlight-icon {
|
||||
width: 130px;
|
||||
height: 305px;
|
||||
background-position: -5px -5px;
|
||||
@ -212,6 +212,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor-wardrobe-figure-preview {
|
||||
background-color: $pale-sky;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
margin: 0 auto;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.avatar-shadow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 25px;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.20);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $pale-sky;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.button-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-avatar-editor {
|
||||
width: $avatar-editor-width;
|
||||
height: $avatar-editor-height;
|
||||
@ -251,9 +296,11 @@
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.spotlight {
|
||||
.avatar-spotlight {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
@ -287,71 +334,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nitro-wardrobe-grid {
|
||||
--nitro-grid-column-min-width: 80px !important;
|
||||
|
||||
.grid-item {
|
||||
height: 140px;
|
||||
max-height: 140px;
|
||||
background-color: $ghost;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 75%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 50%;
|
||||
background-color: $gray-chateau;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-position-y: -23px !important;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.figure-button-container {
|
||||
background-color: $gray-chateau;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item-container {
|
||||
height: 142px !important;
|
||||
max-height: 142px !important;
|
||||
|
||||
.grid-item {
|
||||
background-color: $ghost;
|
||||
|
||||
.avatar-image {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-position-y: -23px !important;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.figure-button-container {
|
||||
background-color: $gray-chateau;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 50%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $gray-chateau;
|
||||
box-shadow: 0 0 8px 2px rgba($white,.6);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { AvatarEditorFigureCategory, FigureSetIdsMessageEvent, GetWardrobeMessageComposer, IAvatarFigureContainer, UserFigureComposer, UserWardrobePageEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import { GetAvatarRenderManager, GetClubMemberLevel, GetSessionDataManager, LocalizeText } from '../../api';
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { GetAvatarRenderManager, GetClubMemberLevel, GetConfiguration, GetSessionDataManager, LocalizeText } from '../../api';
|
||||
import { Button } from '../../common/Button';
|
||||
import { ButtonGroup } from '../../common/ButtonGroup';
|
||||
import { Column } from '../../common/Column';
|
||||
import { Grid } from '../../common/Grid';
|
||||
import { AvatarEditorEvent } from '../../events/avatar-editor';
|
||||
import { CreateMessageHook, SendMessageHook } from '../../hooks';
|
||||
import { useUiEvent } from '../../hooks/events/ui/ui-event';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, NitroLayoutGrid, NitroLayoutGridColumn } from '../../layout';
|
||||
import { AvatarEditorViewProps } from './AvatarEditorView.types';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../layout';
|
||||
import { AvatarEditorAction } from './common/AvatarEditorAction';
|
||||
import { AvatarEditorUtilities } from './common/AvatarEditorUtilities';
|
||||
import { BodyModel } from './common/BodyModel';
|
||||
@ -15,20 +19,14 @@ import { HeadModel } from './common/HeadModel';
|
||||
import { IAvatarEditorCategoryModel } from './common/IAvatarEditorCategoryModel';
|
||||
import { LegModel } from './common/LegModel';
|
||||
import { TorsoModel } from './common/TorsoModel';
|
||||
import { AvatarEditorFigureActionsView } from './views/figure-actions/AvatarEditorFigureActionsView';
|
||||
import { AvatarEditorFigurePreviewView } from './views/figure-preview/AvatarEditorFigurePreviewView';
|
||||
import { AvatarEditorModelView } from './views/model/AvatarEditorModelView';
|
||||
import { AvatarEditorWardrobeView } from './views/wardrobe/AvatarEditorWardrobeView';
|
||||
|
||||
const DEFAULT_MALE_FIGURE: string = 'hr-100.hd-180-7.ch-215-66.lg-270-79.sh-305-62.ha-1002-70.wa-2007';
|
||||
const DEFAULT_FEMALE_FIGURE: string = 'hr-515-33.hd-600-1.ch-635-70.lg-716-66-62.sh-735-68';
|
||||
const MAX_SAVED_FIGURES: number = 10;
|
||||
const ACTION_CLEAR = 'action_clear';
|
||||
const ACTION_RESET = 'action_reset';
|
||||
const ACTION_RANDOMIZE = 'action_randomize';
|
||||
const ACTION_SAVE = 'action_save';
|
||||
|
||||
export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
export const AvatarEditorView: FC<{}> = props =>
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState(false);
|
||||
const [ figures, setFigures ] = useState<Map<string, FigureData>>(null);
|
||||
@ -37,13 +35,15 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
const [ activeCategory, setActiveCategory ] = useState<IAvatarEditorCategoryModel>(null);
|
||||
const [ figureSetIds, setFigureSetIds ] = useState<number[]>([]);
|
||||
const [ boundFurnitureNames, setBoundFurnitureNames ] = useState<string[]>([]);
|
||||
const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>(new Array(MAX_SAVED_FIGURES));
|
||||
const [ savedFigures, setSavedFigures ] = useState<[ IAvatarFigureContainer, string ][]>([]);
|
||||
const [ isWardrobeVisible, setIsWardrobeVisible ] = useState(false);
|
||||
const [ lastFigure, setLastFigure ] = useState<string>(null);
|
||||
const [ lastGender, setLastGender ] = useState<string>(null);
|
||||
const [ needsReset, setNeedsReset ] = useState(false);
|
||||
const [ isInitalized, setIsInitalized ] = useState(false);
|
||||
|
||||
const maxWardrobeSlots = useMemo(() => GetConfiguration<number>('avatar.wardrobe.max.slots', 10), []);
|
||||
|
||||
const onAvatarEditorEvent = useCallback((event: AvatarEditorEvent) =>
|
||||
{
|
||||
switch(event.type)
|
||||
@ -89,7 +89,7 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
|
||||
let i = 0;
|
||||
|
||||
while(i < MAX_SAVED_FIGURES)
|
||||
while(i < maxWardrobeSlots)
|
||||
{
|
||||
savedFigures.push([ null, null ]);
|
||||
|
||||
@ -104,7 +104,7 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
}
|
||||
|
||||
setSavedFigures(savedFigures)
|
||||
}, []);
|
||||
}, [ maxWardrobeSlots ]);
|
||||
|
||||
CreateMessageHook(UserWardrobePageEvent, onUserWardrobePageEvent);
|
||||
|
||||
@ -195,6 +195,11 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
setFigureData(figures.get(gender));
|
||||
}, [ figures ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setSavedFigures(new Array(maxWardrobeSlots));
|
||||
}, [ maxWardrobeSlots ]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!isWardrobeVisible) return;
|
||||
@ -285,18 +290,33 @@ export const AvatarEditorView: FC<AvatarEditorViewProps> = props =>
|
||||
</NitroCardTabsItemView>
|
||||
</NitroCardTabsView>
|
||||
<NitroCardContentView>
|
||||
<NitroLayoutGrid>
|
||||
<NitroLayoutGridColumn size={ 9 }>
|
||||
<Grid>
|
||||
<Column size={ 9 } overflow="hidden">
|
||||
{ (activeCategory && !isWardrobeVisible) &&
|
||||
<AvatarEditorModelView model={ activeCategory } gender={ figureData.gender } setGender={ setGender } /> }
|
||||
{ isWardrobeVisible &&
|
||||
<AvatarEditorWardrobeView figureData={ figureData } savedFigures={ savedFigures } setSavedFigures={ setSavedFigures } loadAvatarInEditor={ loadAvatarInEditor } /> }
|
||||
</NitroLayoutGridColumn>
|
||||
<NitroLayoutGridColumn overflow="hidden" size={ 3 }>
|
||||
</Column>
|
||||
<Column size={ 3 } overflow="hidden">
|
||||
<AvatarEditorFigurePreviewView figureData={ figureData } />
|
||||
<AvatarEditorFigureActionsView processAction={ processAction } />
|
||||
</NitroLayoutGridColumn>
|
||||
</NitroLayoutGrid>
|
||||
<Column grow gap={ 1 }>
|
||||
<ButtonGroup>
|
||||
<Button variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_RESET) }>
|
||||
<FontAwesomeIcon icon="undo" />
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_CLEAR) }>
|
||||
<FontAwesomeIcon icon="trash" />
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_RANDOMIZE) }>
|
||||
<FontAwesomeIcon icon="dice" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Button className="w-100" variant="success" size="sm" onClick={ event => processAction(AvatarEditorAction.ACTION_SAVE) }>
|
||||
{ LocalizeText('avatareditor.save') }
|
||||
</Button>
|
||||
</Column>
|
||||
</Column>
|
||||
</Grid>
|
||||
</NitroCardContentView>
|
||||
</NitroCardView>
|
||||
);
|