Skip to content

Commit

Permalink
Merge branch 'release/v1.64.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
anisus committed Jan 24, 2025
2 parents 1b7a4bd + ebcce5f commit c97ea87
Show file tree
Hide file tree
Showing 18 changed files with 589 additions and 18 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mucklet-client",
"version": "1.63.0",
"version": "1.64.0",
"description": "A web client for Mucklet.",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ class CharSettingsBotToken {
}

dispose() {
this._listen(false);
this.module.pageCharSettings.removeTool('botToken');
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import objectDefault from 'utils/objectDefault';
import formatDateTime from 'utils/formatDateTime';
import CharSettingsBotTokenContent from './CharSettingsBotTokenContent';

const txtNotIssued = l10n.l('charSettingsBotToken.notIssue', "No token issued");;
const txtNotIssued = l10n.l('charSettingsBotToken.notIssue', "No token issued");

class CharSettingsBotTokenComponent {
constructor(module, char, charSettings, state) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import PlayerSettingsManagerTokenComponent from './PlayerSettingsManagerTokenComponent';
import './playerSettingsManagerToken.scss';

/**
* PlayerSettingsManagerToken adds a tool to PagePlayerSettings to create
* manager tokens.
*/
class PlayerSettingsManagerToken {
constructor(app, params) {
this.app = app;

this.app.require([
'auth',
'api',
'pagePlayerSettings',
'toaster',
'confirm',
], this._init.bind(this));
}

_init(module) {
this.module = Object.assign({ self: this }, module);

this.module.pagePlayerSettings.addTool({
id: 'managerToken',
type: 'section',
sortOrder: 25,
componentFactory: (user, player, state) => new PlayerSettingsManagerTokenComponent(this.module, user, state),
});
}

dispose() {
this.module.pagePlayerSettings.removeTool('managerToken');
}
}

export default PlayerSettingsManagerToken;
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Context, Elem, Txt } from 'modapp-base-component';
import { ModelComponent, ModelTxt, CollectionComponent } from 'modapp-resource-component';
import { Model, CollectionWrapper } from 'modapp-resource';
import l10n from 'modapp-l10n';
import PanelSection from 'components/PanelSection';
import FAIcon from 'components/FAIcon';
import Fader from 'components/Fader';
import Collapser from 'components/Collapser';
import objectDefault from 'utils/objectDefault';
import formatDateTime from 'utils/formatDateTime';
import PlayerSettingsManagerTokenContent from './PlayerSettingsManagerTokenContent';

const txtNotIssued = l10n.l('playerSettingsManagerToken.notIssue', "No token issued");

class PlayerSettingsManagerTokenComponent {
constructor(module, user, state) {
this.module = module;
this.user = user;
this.state = objectDefault(state, {
open: false,
});
this.model = new Model({ data: this.state, eventBus: this.module.self.app.eventBus });
}

render(el) {
let components = {};
let contentCollapser = new Collapser();
this.elem = new Context(
() => {
let ctx = { collection: new CollectionWrapper([], { eventBus: this.module.self.app.eventBus }) };
this.module.api.get('auth.user.' + this.user.id + '.tokens')
.then(tokens => {
if (ctx.collection) {
ctx.collection.setCollection(tokens);
}
});
return ctx;
},
ctx => {
ctx.collection.dispose();
ctx.collection = null;
},
ctx => new PanelSection(
l10n.l('playerSettingsManagerToken.managerToken', "Manager token"),
new CollectionComponent(
ctx.collection,
new ModelComponent(
null,
new Elem(n => n.elem('div', { className: 'playersettingsmanagertoken badge', events: {
click: (e, ev) => {
if (ctx.collection?.length) {
this._toggleContent();
ev.stopPropagation();
}
},
}}, [
n.elem('div', { className: 'badge--select' }, [
n.elem('button', {
className: 'badge--faicon iconbtn smallicon solid',
events: {
click: (c, e) => {
let tokens = ctx.collection?._collection; // [TODO] Once the bug is fixed in CollectionWrapper, replace with this: getCollection();
if (tokens) {
this._renewToken(tokens);
}
e.stopPropagation();
},
},
}, [
n.component(new FAIcon('key')),
]),
n.elem('div', { className: 'badge--info' }, [
n.elem('div', { className: 'badge--subtitle' }, [
n.component(new Txt(l10n.l('playerSettingsManagerToken.issued', "Issued"))),
]),
n.component('issued', new Fader("", { className: 'badge--text' })),
]),
]),
n.component(new ModelComponent(
this.model,
contentCollapser,
(m, c) => this._setContent(c, ctx, m, components),
)),
])),
(m, c) => {
let n = c.getNode('issued');
let issued = m && m.issued;
n.setComponent(issued
? components.issued = (components.issued && components.issued.getModel() == m
? components.issued
: new ModelTxt(m, m => formatDateTime(new Date(m.issued), { showYear: true }), { className: 'playersettingsmanagertoken--issued' })
)
: components.notIssued = components.notIssued || new Txt(txtNotIssued, { className: 'playersettingsmanagertoken--notissued' }),
);
},
),
(col, c) => {
let token = col?.length ? col.atIndex(0) : null;
// Ensure we close the content
if (!token) {
this._toggleContent(false);
}
c.getComponent()[token ? 'addClass' : 'removeClass']('btn');
this._setContent(contentCollapser, ctx, this.model, components);
c.setModel(token);
},
),
{
className: 'common--sectionpadding',
noToggle: true,
popupTip: l10n.l('playerSettingsManagerToken.managerTokenInfo', "The token is used as credentials when accessing the API to manage room scripts and other resources using external tools such as mucklet-script.\nCreate or renew the token by clicking the key-icon."),
popupTipClassName: 'popuptip--width-s',
},
),
);
this.elem.render(el);
}

unrender() {
if (this.elem) {
this.elem.unrender();
this.elem = null;
Object.assign(this.state, this.model.props);
}
}

_setContent(c, ctx, model, components) {
let token = ctx.collection?.length && ctx.collection.atIndex(0);
c.setComponent(components.content = model.open && token
? components.content || new PlayerSettingsManagerTokenContent(this.module, this.user, token)
: null,
);
}

_toggleContent(open) {
this.model.set({ open: typeof open == 'undefined'
? !this.model.open
: !!open,
});
}

_renewToken(tokens) {
let token = tokens.atIndex(0) || null;
this.module.confirm.open(() => this._issueToken(tokens, token), {
title: token
? l10n.l('playerSettingsManagerToken.confirmRenewToken', "Confirm renew token")
: l10n.l('playerSettingsManagerToken.confirmTokenCreate', "Confirm create token"),
body: new Elem(n => n.elem('div', [
n.component(new Txt(token
? l10n.l('playerSettingsManagerToken.renewTokenBody', "Do you really wish to renew the manager token?")
: l10n.l('playerSettingsManagerToken.createTokenBody', "Do you really wish to create a manager token?"), { tagName: 'p' },
)),
n.elem('p', { className: 'dialog--error' }, [
n.component(new FAIcon('exclamation-triangle')),
n.html("  "),
n.component(new Txt(token
? l10n.l('playerSettingsManagerToken.renewTokenWarning', "Renewal will disconnect any tool using the previous token.")
: l10n.l('playerSettingsManagerToken.createTokenWarning', "Never share a token with anyone, as it can be used to access your rooms and scripts."),
)),
]),
])),
confirm: token
? l10n.l('playerSettingsManagerToken.renewToken', "Renew token")
: l10n.l('playerSettingsManagerToken.createToken', "Create token"),
});
}

_issueToken(tokens, token) {
return (token ? token.call('renewToken') : tokens.call('create'))
.then(() => {
this._toggleContent(true);
this.module.toaster.open({
title: l10n.l('playerSettingsManagerToken.tokenIssued', "Token issued"),
content: close => new Elem(n => n.elem('div', [
n.component(new Txt(l10n.l('playerSettingsManagerToken.managerTokenIssued', "Manager token issued"), { tagName: 'p' })),
])),
closeOn: 'click',
type: 'success',
autoclose: true,
});
})
.catch(err => this.module.toaster.openError(err, { title: l10n.l('playerSettingsManagerToken.failedToIssueToken', "Failed to issue token") }));
}
}

export default PlayerSettingsManagerTokenComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Elem, Txt } from 'modapp-base-component';
import { ModelTxt } from 'modapp-resource-component';
import l10n from 'modapp-l10n';
import FAIcon from 'components/FAIcon';
import copyToClipboard from 'utils/copyToClipboard';

class PlayerSettingsManagerTokenContent {
constructor(module, user, token) {
this.module = module;
this.user = user;
this.token = token;
}

render(el) {
this.elem = new Elem(n => n.elem('div', { className: 'playersettingsmanagertoken-content' }, [
n.elem('div', { className: 'badge--select badge--margin' }, [
n.elem('div', { className: 'flex-row' }, [
n.component(new Txt(l10n.l('playerSettingsManagerToken.token', "Token"), { className: 'badge--iconcol badge--subtitle badge--micro' })),
n.component(new ModelTxt(this.token, m => m.secret.slice(0, 20) + "...", {
className: 'badge--info badge--text badge--nowrap playersettingsmanagertoken-content--token',
})),
]),
]),
n.elem('div', { className: 'badge--divider' }),
n.elem('div', { className: 'flex-row margin4' }, [
n.elem('button', { className: 'btn icon-left tiny primary flex-1', events: {
click: (el, e) => {
this._copyToken();
e.stopPropagation();
},
}}, [
n.component(new FAIcon('clipboard')),
n.component(new Txt(l10n.l('playerSettingsManagerToken.copy', "Copy to clipboard"))),
]),
n.elem('button', { className: 'iconbtn tiny solid tinyicon flex-auto', events: {
click: (el, e) => {
this._deleteToken();
e.stopPropagation();
},
}}, [
n.component(new FAIcon('trash')),
]),
]),
]));
return this.elem.render(el);
}

unrender() {
if (this.elem) {
this.elem.unrender();
this.elem = null;
}
}

_copyToken() {
copyToClipboard(this.token.secret).then(() => this.module.toaster.open({
title: l10n.l('playerSettingsManagerToken.copiedToClipboard', "Copied to clipboard"),
content: close => new Elem(n => n.elem('div', [
n.component(new Txt(l10n.l('playerSettingsManagerToken.copiedToken', "Copied manager token to clipboard."), { tagName: 'p' })),
])),
closeOn: 'click',
type: 'success',
autoclose: true,
})).catch(err => this.module.toaster.openError(err, {
title: l10n.l('playerSettingsManagerToken.failedToCopyToClipboard', "Failed to copy to clipboard"),
}));
}

_deleteToken() {
this.module.confirm.open(() => this.token.call('delete')
.catch(err => this.module.toaster.openError(err, { title: l10n.l('playerSettingsManagerToken.failedToDeleteToken', "Failed to delete token") })),
{
title: l10n.l('playerSettingsManagerToken.confirmDeleteToken', "Confirm delete token"),
body: new Elem(n => n.elem('div', [
n.component(new Txt(l10n.l('playerSettingsManagerToken.deleteTokenBody', "Do you really wish to delete the manager token?"), { tagName: 'p' },
)),
n.elem('p', { className: 'dialog--error' }, [
n.component(new FAIcon('exclamation-triangle')),
n.html("  "),
n.component(new Txt(l10n.l('playerSettingsManagerToken.deleteTokenWarning', "Deletion will disconnect any tool using the token."))),
]),
])),
confirm: l10n.l('playerSettingsManagerToken.delete', "Delete"),
});
}

}

export default PlayerSettingsManagerTokenContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import '~scss/variables';
@import '~scss/mixins';

.playersettingsmanagertoken {

}

.playersettingsmanagertoken-content {
&--token {
font-size: $font-size-micro;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import ScriptCompileError from 'components/ScriptCompileError';
const usageText = 'create roomscript <span class="param">Keyword</span> = <span class="param">Source</span>';
const shortDesc = 'Create a room script';
const helpText =
`<p class="common--formattext">Create a room script. For more information, see the <a href="https://github.com/mucklet/mucklet-script" target="_blank" rel="noopener noreferrer" title="https://github.com/mucklet/mucklet-script">mucklet-script</a> development resources.</p>
`<p>Create a room script.</p>
<p>For info on how to active the script, type: <code>help set roomscript</code></p>
<p class="common--formattext">For more information and script examples, see the <a href="https://github.com/mucklet/mucklet-script#readme" target="_blank" rel="noopener noreferrer" title="https://github.com/mucklet/mucklet-script">mucklet-script</a> development resources.</p>
<p><code class="param">Keyword</code> is the keyword to use for the script.</p>
<p><code class="param">Source</code> is the room script source code.</p>`;
const examples = [
{ cmd: 'create roomscript test = export function onActivate(): void {\n Room.describe("Hello, world!")\n}', desc: l10n.l('createRoomScript.helloWorldDesc', "Creates a <code>test</code> hello world room script.") },
];

/**
* CreateRoomScript adds command to create a room script.
Expand Down Expand Up @@ -67,6 +72,7 @@ class CreateRoomScript {
usage: l10n.l('createRoomScript.usage', usageText),
shortDesc: l10n.l('createRoomScript.shortDesc', shortDesc),
desc: l10n.l('createRoomScript.helpText', helpText),
examples,
sortOrder: 210,
});
}
Expand Down
Loading

0 comments on commit c97ea87

Please sign in to comment.