mirror of
https://github.com/tobspr/shapez.io.git
synced 2024-10-27 20:34:29 +00:00
Do not allow saving in the demo version
This commit is contained in:
parent
8abe84b120
commit
9c4fe248db
@ -53,8 +53,6 @@
|
||||
|
||||
> .dialogInner {
|
||||
background: #fff;
|
||||
@include S(min-width, 300px);
|
||||
max-width: calc(100vw - #{D(40px)});
|
||||
max-height: calc(100vh - #{D(40px)});
|
||||
@include S(border-radius, $globalBorderRadius);
|
||||
display: flex;
|
||||
@ -118,6 +116,7 @@
|
||||
@include PlainText;
|
||||
overflow-y: auto;
|
||||
pointer-events: all;
|
||||
@include S(width, 350px);
|
||||
}
|
||||
|
||||
> .buttons {
|
||||
|
@ -71,6 +71,7 @@ ingame_HUD_BetaOverlay,
|
||||
ingame_HUD_UnlockNotification,
|
||||
ingame_HUD_Shop,
|
||||
ingame_HUD_Statistics,
|
||||
ingame_HUD_ModalDialogs,
|
||||
ingame_HUD_SettingsMenu;
|
||||
|
||||
$zindex: 100;
|
||||
@ -96,7 +97,7 @@ body.uiHidden {
|
||||
|
||||
body.modalDialogActive,
|
||||
body.ingameDialogOpen {
|
||||
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay) {
|
||||
> *:not(.ingameDialog):not(.modalDialogParent):not(.loadingDialog):not(.gameLoadingOverlay):not(#ingame_HUD_ModalDialogs) {
|
||||
filter: blur(5px) !important;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#state_AboutState {
|
||||
.content {
|
||||
> .container .content {
|
||||
@include PlainText;
|
||||
}
|
||||
}
|
||||
|
@ -21,4 +21,11 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
#ingame_HUD_ModalDialogs {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
@ -53,12 +53,22 @@
|
||||
}
|
||||
|
||||
.mainWrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
@include S(padding, 0, 10px);
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
|
||||
&.noDemo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.demo {
|
||||
@include S(grid-column-gap, 10px);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.standaloneBanner {
|
||||
background: rgb(255, 225, 238);
|
||||
|
@ -41,12 +41,28 @@
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.3;
|
||||
// opacity: 0.3;
|
||||
pointer-events: none;
|
||||
* {
|
||||
pointer-events: none !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
position: relative;
|
||||
.standaloneOnlyHint {
|
||||
@include PlainText;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(#fff, 0.5);
|
||||
text-transform: uppercase;
|
||||
color: $colorRedBright;
|
||||
}
|
||||
}
|
||||
|
||||
.value.enum {
|
||||
|
@ -6,7 +6,7 @@
|
||||
$padding: 15px;
|
||||
|
||||
.headerBar,
|
||||
.content {
|
||||
> .container .content {
|
||||
@include S(width, 500px);
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,18 @@ export const IS_DEBUG =
|
||||
(window.location.host.indexOf("localhost:") >= 0 || window.location.host.indexOf("192.168.0.") >= 0) &&
|
||||
window.location.search.indexOf("nodebug") < 0;
|
||||
|
||||
// export const IS_DEMO = G_IS_PROD;
|
||||
export const IS_DEMO = G_IS_RELEASE;
|
||||
|
||||
const smoothCanvas = true;
|
||||
|
||||
export const THIRDPARTY_URLS = {
|
||||
discord: "https://discord.gg/HN7EVzV",
|
||||
github: "https://github.com/tobspr/shapez.io",
|
||||
|
||||
standaloneStorePage: "https://steam.shapez.io",
|
||||
};
|
||||
|
||||
export const globalConfig = {
|
||||
// Size of a single tile in Pixels.
|
||||
// NOTICE: Update webpack.production.config too!
|
||||
|
@ -12,7 +12,7 @@ import { HUDKeybindingOverlay } from "./parts/keybinding_overlay";
|
||||
import { HUDUnlockNotification } from "./parts/unlock_notification";
|
||||
import { HUDGameMenu } from "./parts/game_menu";
|
||||
import { HUDShop } from "./parts/shop";
|
||||
import { IS_MOBILE, globalConfig } from "../../core/config";
|
||||
import { IS_MOBILE, globalConfig, IS_DEMO } from "../../core/config";
|
||||
import { HUDMassSelector } from "./parts/mass_selector";
|
||||
import { HUDVignetteOverlay } from "./parts/vignette_overlay";
|
||||
import { HUDStatistics } from "./parts/statistics";
|
||||
@ -25,6 +25,7 @@ import { HUDDebugInfo } from "./parts/debug_info";
|
||||
import { HUDEntityDebugger } from "./parts/entity_debugger";
|
||||
import { KEYMAPPINGS } from "../key_action_mapper";
|
||||
import { HUDWatermark } from "./parts/watermark";
|
||||
import { HUDModalDialogs } from "./parts/modal_dialogs";
|
||||
|
||||
export class GameHUD {
|
||||
/**
|
||||
@ -62,6 +63,8 @@ export class GameHUD {
|
||||
|
||||
// betaOverlay: new HUDBetaOverlay(this.root),
|
||||
debugInfo: new HUDDebugInfo(this.root),
|
||||
|
||||
dialogs: new HUDModalDialogs(this.root),
|
||||
};
|
||||
|
||||
this.signals = {
|
||||
@ -78,7 +81,7 @@ export class GameHUD {
|
||||
this.parts.entityDebugger = new HUDEntityDebugger(this.root);
|
||||
}
|
||||
|
||||
if (!G_IS_STANDALONE && G_IS_RELEASE) {
|
||||
if (IS_DEMO) {
|
||||
this.parts.watermark = new HUDWatermark(this.root);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { SOUNDS } from "../../../platform/sound";
|
||||
import { enumNotificationType } from "./notifications";
|
||||
import { T } from "../../../translations";
|
||||
import { KEYMAPPINGS } from "../../key_action_mapper";
|
||||
import { IS_DEMO } from "../../../core/config";
|
||||
|
||||
export class HUDGameMenu extends BaseHUDPart {
|
||||
initialize() {}
|
||||
@ -117,6 +118,13 @@ export class HUDGameMenu extends BaseHUDPart {
|
||||
}
|
||||
|
||||
startSave() {
|
||||
if (IS_DEMO) {
|
||||
this.root.hud.parts.dialogs.showFeatureRestrictionInfo(
|
||||
null,
|
||||
T.dialogs.saveNotPossibleInDemo.desc
|
||||
);
|
||||
}
|
||||
|
||||
this.root.gameState.doSave();
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ import { DynamicDomAttach } from "../dynamic_dom_attach";
|
||||
import { BaseHUDPart } from "../base_hud_part";
|
||||
import { Dialog, DialogLoading, DialogOptionChooser } from "../../../core/modal_dialog_elements";
|
||||
import { makeDiv } from "../../../core/utils";
|
||||
import { T } from "../../../translations";
|
||||
import { THIRDPARTY_URLS } from "../../../core/config";
|
||||
|
||||
export class HUDModalDialogs extends BaseHUDPart {
|
||||
constructor(root, app) {
|
||||
@ -14,7 +16,7 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
super(root);
|
||||
|
||||
/** @type {Application} */
|
||||
this.app = app;
|
||||
this.app = root ? root.app : app;
|
||||
|
||||
this.dialogParent = null;
|
||||
this.dialogStack = [];
|
||||
@ -22,7 +24,7 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
|
||||
// For use inside of the game, implementation of base hud part
|
||||
initialize() {
|
||||
this.dialogParent = document.getElementById("rg_HUD_ModalDialogs");
|
||||
this.dialogParent = document.getElementById("ingame_HUD_ModalDialogs");
|
||||
this.domWatcher = new DynamicDomAttach(this.root, this.dialogParent);
|
||||
}
|
||||
|
||||
@ -35,7 +37,7 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
}
|
||||
|
||||
createElements(parent) {
|
||||
return makeDiv(parent, "rg_HUD_ModalDialogs");
|
||||
return makeDiv(parent, "ingame_HUD_ModalDialogs");
|
||||
}
|
||||
|
||||
// For use outside of the game
|
||||
@ -46,6 +48,11 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
|
||||
// Methods
|
||||
|
||||
/**
|
||||
* @param {string} title
|
||||
* @param {string} text
|
||||
* @param {Array<string>} buttons
|
||||
*/
|
||||
showInfo(title, text, buttons = ["ok:good"]) {
|
||||
const dialog = new Dialog({
|
||||
app: this.app,
|
||||
@ -63,6 +70,11 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
return dialog.buttonSignals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} title
|
||||
* @param {string} text
|
||||
* @param {Array<string>} buttons
|
||||
*/
|
||||
showWarning(title, text, buttons = ["ok:good"]) {
|
||||
const dialog = new Dialog({
|
||||
app: this.app,
|
||||
@ -80,6 +92,38 @@ export class HUDModalDialogs extends BaseHUDPart {
|
||||
return dialog.buttonSignals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} feature
|
||||
* @param {string} textPrefab
|
||||
*/
|
||||
showFeatureRestrictionInfo(feature, textPrefab = T.dialogs.featureRestriction.desc) {
|
||||
const dialog = new Dialog({
|
||||
app: this.app,
|
||||
title: T.dialogs.featureRestriction.title,
|
||||
contentHTML: textPrefab.replace("<feature>", feature),
|
||||
buttons: ["cancel:bad", "getStandalone:good"],
|
||||
type: "warning",
|
||||
});
|
||||
this.internalShowDialog(dialog);
|
||||
|
||||
if (this.app) {
|
||||
this.app.sound.playUiSound(SOUNDS.dialogOk);
|
||||
}
|
||||
|
||||
this.app.analytics.trackUiClick("demo_dialog_show");
|
||||
|
||||
dialog.buttonSignals.cancel.add(() => {
|
||||
this.app.analytics.trackUiClick("demo_dialog_cancel");
|
||||
});
|
||||
|
||||
dialog.buttonSignals.getStandalone.add(() => {
|
||||
this.app.analytics.trackUiClick("demo_dialog_click");
|
||||
window.open(THIRDPARTY_URLS.standaloneStorePage);
|
||||
});
|
||||
|
||||
return dialog.buttonSignals;
|
||||
}
|
||||
|
||||
showOptionChooser(title, options) {
|
||||
const dialog = new DialogOptionChooser({
|
||||
app: this.app,
|
||||
|
@ -14,13 +14,9 @@ export class HUDWatermark extends BaseHUDPart {
|
||||
const w = this.root.gameWidth;
|
||||
|
||||
parameters.context.fillStyle = "#f77";
|
||||
parameters.context.font = "50px GameFont";
|
||||
parameters.context.font = "bold " + this.root.app.getEffectiveUiScale() * 15 + "px GameFont";
|
||||
parameters.context.textAlign = "center";
|
||||
parameters.context.fillText("DEMO VERSION", w / 2, 100);
|
||||
|
||||
parameters.context.fillStyle = "#aaaca9";
|
||||
parameters.context.font = "20px GameFont";
|
||||
parameters.context.fillText("Get shapez.io on steam for the full experience!", w / 2, 140);
|
||||
parameters.context.fillText("DEMO VERSION", w / 2, 50);
|
||||
|
||||
parameters.context.textAlign = "left";
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { BoolSetting, EnumSetting, BaseSetting } from "./setting_types";
|
||||
import { createLogger } from "../core/logging";
|
||||
import { ExplainedResult } from "../core/explained_result";
|
||||
import { THEMES, THEME, applyGameTheme } from "../game/theme";
|
||||
import { IS_DEMO } from "../core/config";
|
||||
|
||||
const logger = createLogger("application_settings");
|
||||
|
||||
@ -66,7 +67,7 @@ export const allApplicationSettings = [
|
||||
app.platformWrapper.setFullscreen(value);
|
||||
}
|
||||
},
|
||||
G_IS_STANDALONE
|
||||
!IS_DEMO
|
||||
),
|
||||
|
||||
new BoolSetting(
|
||||
@ -101,6 +102,7 @@ export const allApplicationSettings = [
|
||||
applyGameTheme(id);
|
||||
document.body.setAttribute("data-theme", id);
|
||||
},
|
||||
enabled: !IS_DEMO,
|
||||
}),
|
||||
|
||||
new EnumSetting("refreshRate", {
|
||||
@ -114,6 +116,7 @@ export const allApplicationSettings = [
|
||||
* @param {Application} app
|
||||
*/
|
||||
(app, id) => {},
|
||||
enabled: !IS_DEMO,
|
||||
}),
|
||||
];
|
||||
|
||||
|
@ -7,6 +7,8 @@ import { T } from "../translations";
|
||||
|
||||
const logger = createLogger("setting_types");
|
||||
|
||||
const standaloneOnlySettingHtml = `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`;
|
||||
|
||||
export class BaseSetting {
|
||||
/**
|
||||
*
|
||||
@ -113,6 +115,7 @@ export class EnumSetting extends BaseSetting {
|
||||
getHtml() {
|
||||
return `
|
||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
||||
${this.enabled ? "" : standaloneOnlySettingHtml}
|
||||
<div class="row">
|
||||
<label>${T.settings.labels[this.id].title}</label>
|
||||
<div class="value enum" data-setting="${this.id}"></div>
|
||||
@ -186,6 +189,8 @@ export class BoolSetting extends BaseSetting {
|
||||
getHtml() {
|
||||
return `
|
||||
<div class="setting cardbox ${this.enabled ? "enabled" : "disabled"}">
|
||||
${this.enabled ? "" : standaloneOnlySettingHtml}
|
||||
|
||||
<div class="row">
|
||||
<label>${T.settings.labels[this.id].title}</label>
|
||||
<div class="value checkbox checked" data-setting="${this.id}">
|
||||
|
@ -3,6 +3,7 @@ import { SOUNDS } from "../platform/sound";
|
||||
import { T } from "../translations";
|
||||
import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper";
|
||||
import { Dialog } from "../core/modal_dialog_elements";
|
||||
import { THIRDPARTY_URLS } from "../core/config";
|
||||
|
||||
export class AboutState extends TextualGameState {
|
||||
constructor() {
|
||||
@ -17,9 +18,9 @@ export class AboutState extends TextualGameState {
|
||||
return `
|
||||
This game is open source and developed by <a href="https://github.com/tobspr" target="_blank">Tobias Springer</a> (this is me).
|
||||
<br><br>
|
||||
If you want to contribute, check out <a href="https://github.com/tobspr/shapez.io" target="_blank">shapez.io on github</a>.
|
||||
If you want to contribute, check out <a href="${THIRDPARTY_URLS.github}" target="_blank">shapez.io on github</a>.
|
||||
<br><br>
|
||||
This game wouldn't have been possible without the great discord community arround my games - You should really join the <a href="https://discord.gg/HN7EVzV" target="_blank">discord server</a>!
|
||||
This game wouldn't have been possible without the great discord community arround my games - You should really join the <a href="${THIRDPARTY_URLS.discord}" target="_blank">discord server</a>!
|
||||
<br><br>
|
||||
The soundtrack was made by <a href="https://soundcloud.com/pettersumelius" target="_blank">Peppsen</a> - He's awesome.
|
||||
<br><br>
|
||||
|
@ -3,6 +3,7 @@ import { SOUNDS } from "../platform/sound";
|
||||
import { T } from "../translations";
|
||||
import { KEYMAPPINGS, getStringForKeyCode } from "../game/key_action_mapper";
|
||||
import { Dialog } from "../core/modal_dialog_elements";
|
||||
import { IS_DEMO } from "../core/config";
|
||||
|
||||
export class KeybindingsState extends TextualGameState {
|
||||
constructor() {
|
||||
@ -81,6 +82,11 @@ export class KeybindingsState extends TextualGameState {
|
||||
}
|
||||
|
||||
editKeybinding(id) {
|
||||
if (IS_DEMO) {
|
||||
this.dialogs.showFeatureRestrictionInfo(T.demo.features.customizeKeybindings);
|
||||
return;
|
||||
}
|
||||
|
||||
const dialog = new Dialog({
|
||||
app: this.app,
|
||||
title: T.dialogs.editKeybinding.title,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { GameState } from "../core/game_state";
|
||||
import { cachebust } from "../core/cachebust";
|
||||
import { globalConfig } from "../core/config";
|
||||
import { globalConfig, IS_DEBUG, IS_DEMO, THIRDPARTY_URLS } from "../core/config";
|
||||
import {
|
||||
makeDiv,
|
||||
formatSecondsToTimeAgo,
|
||||
@ -41,57 +41,36 @@ export class MainMenuState extends GameState {
|
||||
|
||||
<div class="logo">
|
||||
<img src="${cachebust("res/logo.png")}" alt="shapez.io Logo">
|
||||
|
||||
${
|
||||
G_IS_STANDALONE
|
||||
? ""
|
||||
: `
|
||||
<div class="demoBadge"></div>
|
||||
`
|
||||
}
|
||||
${IS_DEMO ? `<div class="demoBadge"></div>` : ""}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mainWrapper">
|
||||
<div class="mainWrapper ${IS_DEMO ? "demo" : "noDemo"}">
|
||||
|
||||
${IS_DEMO ? `<div class="standaloneBanner leftSide">${bannerHtml}</div>` : ""}
|
||||
|
||||
${
|
||||
G_IS_STANDALONE
|
||||
? ""
|
||||
: `
|
||||
<div class="standaloneBanner leftSide">${bannerHtml}</div>
|
||||
`
|
||||
}
|
||||
<div class="mainContainer">
|
||||
${
|
||||
isSupportedBrowser()
|
||||
? ""
|
||||
: `
|
||||
<div class="browserWarning">${T.mainMenu.browserWarning}</div>
|
||||
`
|
||||
: `<div class="browserWarning">${T.mainMenu.browserWarning}</div>`
|
||||
}
|
||||
|
||||
<button class="playButton styledButton">${T.mainMenu.play}</button>
|
||||
<button class="importButton styledButton">${T.mainMenu.importSavegame}</button>
|
||||
</div>
|
||||
|
||||
${
|
||||
G_IS_STANDALONE
|
||||
? ""
|
||||
: `
|
||||
<div class="standaloneBanner rightSide">${bannerHtml}</div>
|
||||
`
|
||||
}
|
||||
${IS_DEMO ? `<div class="standaloneBanner leftSide">${bannerHtml}</div>` : ""}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
|
||||
<a href="https://github.com/tobspr/shapez.io" target="_blank">
|
||||
<a href="${THIRDPARTY_URLS.github}" target="_blank">
|
||||
${T.mainMenu.openSourceHint}
|
||||
<span class="thirdpartyLogo githubLogo"></span>
|
||||
</a>
|
||||
|
||||
<a href="https://discord.gg/HN7EVzV" target="_blank">
|
||||
<a href="${THIRDPARTY_URLS.discord}" target="_blank">
|
||||
${T.mainMenu.discordLink}
|
||||
<span class="thirdpartyLogo discordLogo"></span>
|
||||
</a>
|
||||
@ -101,6 +80,11 @@ export class MainMenuState extends GameState {
|
||||
}
|
||||
|
||||
requestImportSavegame() {
|
||||
if (IS_DEMO) {
|
||||
this.dialogs.showFeatureRestrictionInfo(T.demo.features.importingGames);
|
||||
return;
|
||||
}
|
||||
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".bin";
|
||||
@ -260,6 +244,12 @@ export class MainMenuState extends GameState {
|
||||
*/
|
||||
resumeGame(game) {
|
||||
this.app.analytics.trackUiClick("resume_game");
|
||||
|
||||
if (IS_DEMO) {
|
||||
this.dialogs.showFeatureRestrictionInfo(T.demo.features.restoringGames);
|
||||
return;
|
||||
}
|
||||
|
||||
const savegame = this.app.savegameMgr.getSavegameById(game.internalId);
|
||||
savegame.readAsync().then(() => {
|
||||
this.moveToState("InGameState", {
|
||||
|
@ -59,11 +59,11 @@ demoBanners:
|
||||
# This is the "advertisement" shown in the main menu and other various places
|
||||
title: This is a demo version
|
||||
intro: >-
|
||||
Get <strong>shapez.io on steam</strong> for:
|
||||
Get <strong>shapez.io on steam</strong> to:
|
||||
advantages:
|
||||
- Save and resume your games.
|
||||
- No advertisements.
|
||||
- Unlimited savegame slots.
|
||||
- Supporting the developer ❤️
|
||||
- Support the developer ❤️
|
||||
|
||||
mainMenu:
|
||||
play: Play
|
||||
@ -83,6 +83,7 @@ dialogs:
|
||||
later: Later
|
||||
restart: Restart
|
||||
reset: Reset
|
||||
getStandalone: Get Standalone
|
||||
|
||||
importSavegameError:
|
||||
title: Import Error
|
||||
@ -126,6 +127,13 @@ dialogs:
|
||||
title: Keybindings reset
|
||||
desc: The keybindings have been reset to their respective defaults!
|
||||
|
||||
featureRestriction:
|
||||
title: Demo Version
|
||||
desc: You tried to access a feature (<feature>) which is not available in the demo. Consider to get the standalone for the full experience!
|
||||
|
||||
saveNotPossibleInDemo:
|
||||
desc: Your game has been saved, but restoring it is only possible in the standalone version. Consider to get the standalone for the full experience!
|
||||
|
||||
ingame:
|
||||
# This is shown in the top left corner and displays useful keybindings in
|
||||
# every situation
|
||||
@ -337,7 +345,7 @@ settings:
|
||||
theme:
|
||||
title: Game theme
|
||||
description: >-
|
||||
Choose the game theme which mainly affects the map background. Notice that everything except the light theme may lead to graphical issues.
|
||||
Choose the game theme (light / dark).
|
||||
|
||||
refreshRate:
|
||||
title: Simulation Target
|
||||
@ -399,3 +407,11 @@ keybindings:
|
||||
|
||||
about:
|
||||
title: About this Game
|
||||
|
||||
demo:
|
||||
features:
|
||||
restoringGames: Restoring savegames
|
||||
importingGames: Importing savegames
|
||||
customizeKeybindings: Customizing Keybindings
|
||||
|
||||
settingNotAvailable: Not available in the demo.
|
||||
|
Loading…
Reference in New Issue
Block a user