mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-05 10:38:30 +00:00
4.0.0-rc8.6
* Fixed: Update Fine to deal with changes to how the React Root is stored in the DOM. Behind the Scenes * Working on the UI for Translation Editing * Refactor some of the main menu dialog logic into a more generic Dialog class
This commit is contained in:
parent
154a587c87
commit
b8f86fe48f
14 changed files with 570 additions and 180 deletions
|
@ -10,9 +10,10 @@
|
||||||
"dev": "webpack-dev-server --config webpack.web.dev.js",
|
"dev": "webpack-dev-server --config webpack.web.dev.js",
|
||||||
"dev:clips": "webpack-dev-server --config webpack.clips.dev.js",
|
"dev:clips": "webpack-dev-server --config webpack.clips.dev.js",
|
||||||
"dev:babel": "webpack-dev-server --config webpack.web.dev.babel.js",
|
"dev:babel": "webpack-dev-server --config webpack.web.dev.babel.js",
|
||||||
"build:all": "npm run build && npm run build:babel && npm run build:clips && npm run build:clips:babel",
|
"build:all": "npm run build && npm run build:clips",
|
||||||
"build": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production'",
|
"build": "npm run build:prod && npm run build:babel",
|
||||||
"build:clips": "webpack --config webpack.clips.prod.js --define process.env.NODE_ENV='production'",
|
"build:clips": "npm run build:clips:prod && npm run build:clips:babel",
|
||||||
|
"build:clips:prod": "webpack --config webpack.clips.prod.js --define process.env.NODE_ENV='production'",
|
||||||
"build:clips:babel": "webpack --config webpack.clips.babel.js --define process.env.NODE_ENV='production'",
|
"build:clips:babel": "webpack --config webpack.clips.babel.js --define process.env.NODE_ENV='production'",
|
||||||
"build:stats": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production' --json > stats.json",
|
"build:stats": "webpack --config webpack.web.prod.js --define process.env.NODE_ENV='production' --json > stats.json",
|
||||||
"build:babel": "webpack --config webpack.web.babel.js --define process.env.NODE_ENV='production'",
|
"build:babel": "webpack --config webpack.web.babel.js --define process.env.NODE_ENV='production'",
|
||||||
|
|
69
src/i18n.js
69
src/i18n.js
|
@ -7,7 +7,7 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import {SERVER} from 'utilities/constants';
|
import {SERVER} from 'utilities/constants';
|
||||||
import {get, pick_random, has} from 'utilities/object';
|
import {get, pick_random, has, timeout} from 'utilities/object';
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,6 +69,8 @@ export class TranslationManager extends Module {
|
||||||
super(...args);
|
super(...args);
|
||||||
this.inject('settings');
|
this.inject('settings');
|
||||||
|
|
||||||
|
this._seen = new Set;
|
||||||
|
|
||||||
this.availableLocales = ['en']; //, 'de', 'ja'];
|
this.availableLocales = ['en']; //, 'de', 'ja'];
|
||||||
|
|
||||||
this.localeData = {
|
this.localeData = {
|
||||||
|
@ -141,6 +143,12 @@ export class TranslationManager extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ( window.BroadcastChannel ) {
|
||||||
|
const bc = this._broadcaster = new BroadcastChannel('ffz-i18n');
|
||||||
|
bc.addEventListener('message',
|
||||||
|
this._boundHandleMessage = this.handleMessage.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
this._.transformation = TRANSFORMATIONS[this.settings.get('i18n.debug.transform')];
|
this._.transformation = TRANSFORMATIONS[this.settings.get('i18n.debug.transform')];
|
||||||
this.locale = this.settings.get('i18n.locale');
|
this.locale = this.settings.get('i18n.locale');
|
||||||
}
|
}
|
||||||
|
@ -154,6 +162,55 @@ export class TranslationManager extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
handleMessage(event) {
|
||||||
|
const msg = event.data;
|
||||||
|
if ( msg.type === 'seen' )
|
||||||
|
this.see(msg.key, true);
|
||||||
|
|
||||||
|
else if ( msg.type === 'request-keys' ) {
|
||||||
|
this.broadcast({type: 'keys', keys: Array.from(this._seen)})
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( msg.type === 'keys' )
|
||||||
|
this.emit(':receive-keys', msg.keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getKeys() {
|
||||||
|
this.broadcast({type: 'request-keys'});
|
||||||
|
|
||||||
|
let data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await timeout(this.waitFor(':receive-keys'), 100);
|
||||||
|
} catch(err) { /* no-op */ }
|
||||||
|
|
||||||
|
if ( data )
|
||||||
|
for(const val of data)
|
||||||
|
this._seen.add(val);
|
||||||
|
|
||||||
|
return this._seen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
broadcast(msg) {
|
||||||
|
if ( this._broadcaster )
|
||||||
|
this._broadcaster.postMessage(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
see(key, from_broadcast = false) {
|
||||||
|
if ( this._seen.has(key) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._seen.add(key);
|
||||||
|
this.emit(':seen', key);
|
||||||
|
|
||||||
|
if ( ! from_broadcast )
|
||||||
|
this.broadcast({type: 'seen', key});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
toLocaleString(thing) {
|
toLocaleString(thing) {
|
||||||
if ( thing && thing.toLocaleString )
|
if ( thing && thing.toLocaleString )
|
||||||
return thing.toLocaleString(this._.locale);
|
return thing.toLocaleString(this._.locale);
|
||||||
|
@ -363,12 +420,14 @@ export class TranslationManager extends Module {
|
||||||
return this._.formatNumber(...args);
|
return this._.formatNumber(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
t(...args) {
|
t(key, ...args) {
|
||||||
return this._.t(...args);
|
this.see(key);
|
||||||
|
return this._.t(key, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
tList(...args) {
|
tList(key, ...args) {
|
||||||
return this._.tList(...args);
|
this.see(key);
|
||||||
|
return this._.tList(key, ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
|
||||||
FrankerFaceZ.Logger = Logger;
|
FrankerFaceZ.Logger = Logger;
|
||||||
|
|
||||||
const VER = FrankerFaceZ.version_info = {
|
const VER = FrankerFaceZ.version_info = {
|
||||||
major: 4, minor: 0, revision: 0, extra: '-rc8.5',
|
major: 4, minor: 0, revision: 0, extra: '-rc8.6',
|
||||||
commit: __git_commit__,
|
commit: __git_commit__,
|
||||||
build: __webpack_hash__,
|
build: __webpack_hash__,
|
||||||
toString: () =>
|
toString: () =>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div
|
<div
|
||||||
:class="{ maximized: maximized || exclusive, exclusive, faded }"
|
:class="{ maximized: maximized || exclusive, exclusive, faded }"
|
||||||
class="ffz-main-menu tw-elevation-3 tw-c-background-alt tw-c-text tw-border tw-flex tw-flex-nowrap tw-flex-column"
|
class="ffz-dialog ffz-main-menu tw-elevation-3 tw-c-background-alt tw-c-text tw-border tw-flex tw-flex-nowrap tw-flex-column"
|
||||||
>
|
>
|
||||||
<header class="tw-c-background tw-full-width tw-align-items-center tw-flex tw-flex-nowrap" @dblclick="resize">
|
<header class="tw-c-background tw-full-width tw-align-items-center tw-flex tw-flex-nowrap" @dblclick="resize">
|
||||||
<h3 class="ffz-i-zreknarf ffz-i-pd-1">FrankerFaceZ</h3>
|
<h3 class="ffz-i-zreknarf ffz-i-pd-1">FrankerFaceZ</h3>
|
||||||
|
|
|
@ -8,11 +8,9 @@ import Module from 'utilities/module';
|
||||||
import {createElement} from 'utilities/dom';
|
import {createElement} from 'utilities/dom';
|
||||||
import {has, deep_copy} from 'utilities/object';
|
import {has, deep_copy} from 'utilities/object';
|
||||||
|
|
||||||
import {parse_path} from 'src/settings';
|
import Dialog from 'utilities/dialog';
|
||||||
|
|
||||||
const EXCLUSIVE_SELECTOR = '.twilight-main,.twilight-minimal-root>div,.twilight-root>.tw-full-height,.clips-root',
|
import {parse_path} from 'src/settings';
|
||||||
MAXIMIZED_SELECTOR = '.twilight-main,.twilight-minimal-root,.twilight-root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area',
|
|
||||||
SELECTOR = '.twilight-root>.tw-full-height,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
|
|
||||||
|
|
||||||
function format_term(term) {
|
function format_term(term) {
|
||||||
return term.replace(/<[^>]*>/g, '').toLocaleLowerCase();
|
return term.replace(/<[^>]*>/g, '').toLocaleLowerCase();
|
||||||
|
@ -37,10 +35,7 @@ export default class MainMenu extends Module {
|
||||||
this._settings_tree = null;
|
this._settings_tree = null;
|
||||||
this._settings_count = 0;
|
this._settings_count = 0;
|
||||||
|
|
||||||
this._menu = null;
|
this.dialog = new Dialog(() => this.buildDialog());
|
||||||
this._visible = true;
|
|
||||||
this._maximized = false;
|
|
||||||
this.exclusive = false;
|
|
||||||
this.has_update = false;
|
this.has_update = false;
|
||||||
|
|
||||||
this.settings.addUI('profiles', {
|
this.settings.addUI('profiles', {
|
||||||
|
@ -101,109 +96,43 @@ export default class MainMenu extends Module {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get maximized() {
|
|
||||||
return this._maximized;
|
|
||||||
}
|
|
||||||
|
|
||||||
set maximized(val) {
|
async onEnable() {
|
||||||
val = Boolean(val);
|
await this.site.awaitElement(Dialog.EXCLUSIVE);
|
||||||
if ( val === this._maximized )
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ( this.enabled )
|
this.dialog.on('hide', this.destroyDialog, this);
|
||||||
this.toggleSize();
|
this.dialog.on('resize', () => {
|
||||||
}
|
if ( this._vue )
|
||||||
|
this._vue.$children[0].maximized = this.dialog.maximized
|
||||||
|
});
|
||||||
|
|
||||||
get visible() {
|
this.on('site.menu_button:clicked', this.dialog.toggleVisible, this.dialog);
|
||||||
return this._visible;
|
this.dialog.show();
|
||||||
}
|
|
||||||
|
|
||||||
set visible(val) {
|
|
||||||
val = Boolean(val);
|
|
||||||
if ( val === this._visible )
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ( this.enabled )
|
|
||||||
this.toggleVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async onEnable(event) {
|
|
||||||
await this.site.awaitElement(EXCLUSIVE_SELECTOR);
|
|
||||||
|
|
||||||
this.on('site.menu_button:clicked', this.toggleVisible);
|
|
||||||
if ( this._visible ) {
|
|
||||||
this._visible = false;
|
|
||||||
this.toggleVisible(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisable() {
|
onDisable() {
|
||||||
if ( this._visible ) {
|
this.dialog.hide();
|
||||||
this.toggleVisible();
|
this.off('site.menu_button:clicked', this.dialog.toggleVisible, this.dialog);
|
||||||
this._visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.off('site.menu_button:clicked', this.toggleVisible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getContainer() {
|
|
||||||
if ( this.exclusive )
|
|
||||||
return document.querySelector(EXCLUSIVE_SELECTOR);
|
|
||||||
|
|
||||||
if ( this._maximized )
|
buildDialog() {
|
||||||
return document.querySelector(MAXIMIZED_SELECTOR);
|
if ( this._menu )
|
||||||
|
return this._menu;
|
||||||
|
|
||||||
return document.querySelector(SELECTOR);
|
this._vue = new this.vue.Vue({
|
||||||
|
el: createElement('div'),
|
||||||
|
render: h => h('main-menu', this.getData())
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._menu = this._vue.$el;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleVisible(event) {
|
destroyDialog() {
|
||||||
if ( event && event.button !== 0 )
|
if ( this._vue )
|
||||||
return;
|
this._vue.$destroy();
|
||||||
|
|
||||||
const maximized = this._maximized,
|
this._menu = this._vue = null;
|
||||||
visible = this._visible = !this._visible,
|
|
||||||
main = this.getContainer();
|
|
||||||
|
|
||||||
if ( ! visible ) {
|
|
||||||
if ( maximized )
|
|
||||||
main.classList.remove('ffz-has-menu');
|
|
||||||
|
|
||||||
if ( this._menu ) {
|
|
||||||
this._menu.remove();
|
|
||||||
this._vue.$destroy();
|
|
||||||
this._menu = this._vue = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! this._menu )
|
|
||||||
this.createMenu();
|
|
||||||
|
|
||||||
if ( maximized )
|
|
||||||
main.classList.add('ffz-has-menu');
|
|
||||||
|
|
||||||
main.appendChild(this._menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSize(event) {
|
|
||||||
if ( ! this._visible || event && event.button !== 0 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
const maximized = this._maximized = !this._maximized,
|
|
||||||
main = this.getContainer(),
|
|
||||||
old_main = this._menu.parentElement;
|
|
||||||
|
|
||||||
if ( maximized )
|
|
||||||
main.classList.add('ffz-has-menu');
|
|
||||||
else
|
|
||||||
old_main.classList.remove('ffz-has-menu');
|
|
||||||
|
|
||||||
this._menu.remove();
|
|
||||||
main.appendChild(this._menu);
|
|
||||||
|
|
||||||
this._vue.$children[0].maximized = maximized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -588,32 +517,22 @@ export default class MainMenu extends Module {
|
||||||
settings.keys['home'], // settings[0],
|
settings.keys['home'], // settings[0],
|
||||||
nav_keys: settings.keys,
|
nav_keys: settings.keys,
|
||||||
|
|
||||||
maximized: this._maximized,
|
maximized: this.dialog.maximized,
|
||||||
resize: e => !this.exclusive && this.toggleSize(e),
|
exclusive: this.dialog.exclusive,
|
||||||
close: e => !this.exclusive && this.toggleVisible(e),
|
|
||||||
|
resize: e => ! this.dialog.exclusive && this.dialog.toggleSize(e),
|
||||||
|
close: e => ! this.dialog.exclusive && this.dialog.toggleVisible(e),
|
||||||
|
|
||||||
popout: e => {
|
popout: e => {
|
||||||
if ( this.exclusive )
|
if ( this.dialog.exclusive )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.toggleVisible(e);
|
this.dialog.toggleVisible(e);
|
||||||
if ( ! this.openPopout() )
|
if ( ! this.openPopout() )
|
||||||
alert(this.i18n.t('popup.error', 'We tried opening a pop-up window and could not. Make sure to allow pop-ups from Twitch.'));
|
alert(this.i18n.t('popup.error', 'We tried opening a pop-up window and could not. Make sure to allow pop-ups from Twitch.')); // eslint-disable-line no-alert
|
||||||
},
|
},
|
||||||
version: window.FrankerFaceZ.version_info,
|
|
||||||
|
|
||||||
exclusive: this.exclusive
|
version: window.FrankerFaceZ.version_info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createMenu() {
|
|
||||||
if ( this._menu )
|
|
||||||
return;
|
|
||||||
|
|
||||||
this._vue = new this.vue.Vue({
|
|
||||||
el: createElement('div'),
|
|
||||||
render: h => h('main-menu', this.getData())
|
|
||||||
});
|
|
||||||
|
|
||||||
this._menu = this._vue.$el;
|
|
||||||
}
|
|
||||||
}
|
}
|
130
src/modules/translation_ui/components/translation-ui.vue
Normal file
130
src/modules/translation_ui/components/translation-ui.vue
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
:class="{ maximized: maximized || exclusive, exclusive, faded }"
|
||||||
|
class="ffz-dialog tw-elevation-3 tw-c-background-alt tw-c-text tw-border tw-flex tw-flex-nowrap tw-flex-column"
|
||||||
|
>
|
||||||
|
<header class="tw-c-background tw-full-width tw-align-items-center tw-flex tw-flex-nowrap" @dblclick="resize">
|
||||||
|
<h3 class="ffz-i-zreknarf ffz-i-pd-1">{{ t('i18n.ui.title', 'Translation Editor') }}</h3>
|
||||||
|
<div class="tw-flex-grow-1 tw-pd-x-2">
|
||||||
|
<div class="tw-search-input">
|
||||||
|
<label for="ffz-main-menu.search" class="tw-hide-accessible">{{ t('i18n.ui.search', 'Search Strings') }}</label>
|
||||||
|
<div class="tw-relative">
|
||||||
|
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||||
|
<figure class="ffz-i-search" />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="ffz-main-menu.search"
|
||||||
|
v-model="query"
|
||||||
|
:placeholder="t('i18n.ui.search', 'Search Strings')"
|
||||||
|
type="search"
|
||||||
|
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button v-if="!maximized && !exclusive" class="tw-button-icon tw-mg-x-05" @click="faded = ! faded">
|
||||||
|
<span class="tw-button-icon__icon">
|
||||||
|
<figure :class="faded ? 'ffz-i-eye-off' : 'ffz-i-eye'" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button v-if="!exclusive" class="tw-button-icon tw-mg-x-05 tw-tooltip-wrapper" @click="popout">
|
||||||
|
<span class="tw-button-icon__icon">
|
||||||
|
<figure class="ffz-i-link-ext" />
|
||||||
|
</span>
|
||||||
|
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-center">
|
||||||
|
{{ t('i18n.ui.popout', 'Open the Translation Editor in a New Window') }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button v-if="!exclusive" class="tw-button-icon tw-mg-x-05" @click="resize">
|
||||||
|
<span class="tw-button-icon__icon">
|
||||||
|
<figure :class="{'ffz-i-window-maximize': !maximized, 'ffz-i-window-restore': maximized}" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button v-if="!exclusive" class="tw-button-icon tw-mg-x-05" @click="close">
|
||||||
|
<span class="tw-button-icon__icon">
|
||||||
|
<figure class="ffz-i-window-close" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<section class="tw-border-t tw-full-height tw-full-width tw-flex tw-flex-nowrap tw-overflow-hidden">
|
||||||
|
<div v-for="(key, idx) of phrases" :key="idx" class="tw-block tw-mg-1">
|
||||||
|
{{ key }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import displace from 'displacejs';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return this.$vnode.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
filter() {
|
||||||
|
return this.query.toLowerCase()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
maximized() {
|
||||||
|
this.updateDrag();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.updateDrag();
|
||||||
|
|
||||||
|
this._on_resize = this.handleResize.bind(this);
|
||||||
|
window.addEventListener('resize', this._on_resize);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.destroyDrag();
|
||||||
|
|
||||||
|
if ( this._on_resize ) {
|
||||||
|
window.removeEventListener('resize', this._on_resize);
|
||||||
|
this._on_resize = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateDrag() {
|
||||||
|
if ( this.maximized )
|
||||||
|
this.destroyDrag();
|
||||||
|
else
|
||||||
|
this.createDrag();
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyDrag() {
|
||||||
|
if ( this.displace ) {
|
||||||
|
this.displace.destroy();
|
||||||
|
this.displace = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createDrag() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if ( ! this.maximized )
|
||||||
|
this.displace = displace(this.$el, {
|
||||||
|
handle: this.$el.querySelector('header'),
|
||||||
|
highlightInputs: true,
|
||||||
|
constrain: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleResize() {
|
||||||
|
if ( this.displace )
|
||||||
|
this.displace.reinit();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
118
src/modules/translation_ui/index.js
Normal file
118
src/modules/translation_ui/index.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Translation UI
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import Module from 'utilities/module';
|
||||||
|
import Dialog from 'utilities/dialog';
|
||||||
|
|
||||||
|
import {createElement} from 'utilities/dom';
|
||||||
|
|
||||||
|
export default class TranslationUI extends Module {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
|
||||||
|
this.inject('settings');
|
||||||
|
this.inject('i18n');
|
||||||
|
this.inject('site');
|
||||||
|
this.inject('vue');
|
||||||
|
|
||||||
|
this.load_requires = ['vue'];
|
||||||
|
|
||||||
|
this.dialog = new Dialog(() => this.buildDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
openPopout() {
|
||||||
|
const win = window.open(
|
||||||
|
'https://twitch.tv/popout/frankerfacez/chat?ffz-translate',
|
||||||
|
'_blank',
|
||||||
|
'resizable=yes,scrollbars=yes,width=850,height=600'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( win ) {
|
||||||
|
win.focus();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.log.warn('Unable to open popout translation window.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLoad() {
|
||||||
|
this.vue.component(
|
||||||
|
(await import(/* webpackChunkName: "translation-ui" */ './components.js')).default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onEnable() {
|
||||||
|
await this.site.awaitElement(Dialog.EXCLUSIVE);
|
||||||
|
|
||||||
|
this.on('i18n:seen', key => {
|
||||||
|
if ( ! this._vue )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const comp = this._vue.$children[0];
|
||||||
|
comp.phrases.push(key);
|
||||||
|
})
|
||||||
|
|
||||||
|
this.dialog.on('hide', this.destroyDialog, this);
|
||||||
|
this.dialog.on('resize', () => {
|
||||||
|
if ( this._vue )
|
||||||
|
this._vue.$children[0].maximized = this.dialog.maximized
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDisable() {
|
||||||
|
this.dialog.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async buildDialog() {
|
||||||
|
if ( this._dialog )
|
||||||
|
return this._dialog;
|
||||||
|
|
||||||
|
const data = await this.getData();
|
||||||
|
|
||||||
|
this._vue = new this.vue.Vue({
|
||||||
|
el: createElement('div'),
|
||||||
|
render: h => h('translation-ui', data)
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._dialog = this._vue.$el;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyDialog() {
|
||||||
|
if ( this._vue )
|
||||||
|
this._vue.$destroy();
|
||||||
|
|
||||||
|
this._dialog = this._vue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getData() {
|
||||||
|
return {
|
||||||
|
query: '',
|
||||||
|
|
||||||
|
faded: false,
|
||||||
|
maximized: this.dialog.maximized,
|
||||||
|
exclusive: this.dialog.exclusive,
|
||||||
|
|
||||||
|
phrases: Array.from(await this.i18n.getKeys()),
|
||||||
|
|
||||||
|
resize: e => ! this.dialog.exclusive && this.dialog.toggleSize(e),
|
||||||
|
close: e => ! this.dialog.exclusive && this.dialog.toggleVisible(e),
|
||||||
|
|
||||||
|
popout: e => {
|
||||||
|
if ( this.dialog.exclusive )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.dialog.toggleVisible(e);
|
||||||
|
if ( ! this.openPopout() )
|
||||||
|
alert(this.i18n.t('popup.error', 'We tried opening a pop-up window and could not. Make sure to allow pop-ups from Twitch.')); // eslint-disable-line no-alert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Translation UI
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
|
||||||
//import {createElement} from 'utilities/dom';
|
|
||||||
|
|
||||||
export default class TranslationUI extends Module {
|
|
||||||
constructor(...args) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
this.inject('settings');
|
|
||||||
this.inject('site');
|
|
||||||
this.inject('vue');
|
|
||||||
|
|
||||||
//this.should_enable = true;
|
|
||||||
|
|
||||||
this._dialog = null;
|
|
||||||
this._visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onLoad() {
|
|
||||||
this.vue.component(
|
|
||||||
(await import(/* webpackChunkName: "translation-ui" */ './components.js')).default
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async onEnable() {
|
|
||||||
await this.site.awaitElement('.twilight-root');
|
|
||||||
this.ps = this.site.web_munch.getModule('ps');
|
|
||||||
}
|
|
||||||
|
|
||||||
onDisable() {
|
|
||||||
if ( this._visible ) {
|
|
||||||
this.toggleVisible();
|
|
||||||
this._visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -87,3 +87,8 @@ export default class Clippy extends BaseSite {
|
||||||
return this._user = session && session.user;
|
return this._user = session && session.user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Clippy.DIALOG_EXCLUSIVE = '.clips-root';
|
||||||
|
Clippy.DIALOG_MAXIMIZED = '.clips-root>.tw-full-height .scrollable-area';
|
||||||
|
Clippy.DIALOG_SELECTOR = '.clips-root>.tw-full-height .scrollable-area';
|
|
@ -78,10 +78,18 @@ export default class Twilight extends BaseSite {
|
||||||
// Check for ?ffz-settings in page and open the
|
// Check for ?ffz-settings in page and open the
|
||||||
// settings window in exclusive mode.
|
// settings window in exclusive mode.
|
||||||
const params = new URL(window.location).searchParams;
|
const params = new URL(window.location).searchParams;
|
||||||
if (params && params.has('ffz-settings')) {
|
if ( params ) {
|
||||||
const main_menu = this.resolve('main_menu');
|
if ( params.has('ffz-settings') ) {
|
||||||
main_menu.exclusive = true;
|
const main_menu = this.resolve('main_menu');
|
||||||
main_menu.enable();
|
main_menu.dialog.exclusive = true;
|
||||||
|
main_menu.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( params.has('ffz-translate') ) {
|
||||||
|
const translation = this.resolve('translation_ui');
|
||||||
|
translation.dialog.exclusive = true;
|
||||||
|
translation.enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,3 +191,8 @@ Twilight.ROUTES = {
|
||||||
'prime': '/prime',
|
'prime': '/prime',
|
||||||
'user': '/:userName'
|
'user': '/:userName'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Twilight.DIALOG_EXCLUSIVE = '.twilight-main,.twilight-minimal-root>div,.twilight-root>.tw-full-height,.clips-root';
|
||||||
|
Twilight.DIALOG_MAXIMIZED = '.twilight-main,.twilight-minimal-root,.twilight-root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
|
||||||
|
Twilight.DIALOG_SELECTOR = '.twilight-root>.tw-full-height,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
|
|
@ -34,6 +34,9 @@ export default class Fine extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.react_root = this.root_element._reactRootContainer;
|
this.react_root = this.root_element._reactRootContainer;
|
||||||
|
if ( this.react_root._internalRoot && this.react_root._internalRoot.current )
|
||||||
|
this.react_root = this.react_root._internalRoot;
|
||||||
|
|
||||||
this.react = this.react_root.current.child;
|
this.react = this.react_root.current.child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ export default class Fine extends Module {
|
||||||
if ( ! this.accessor )
|
if ( ! this.accessor )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return element[this.accessor] || (element._reactRootContainer && element._reactRootContainer.current);
|
return element[this.accessor] || (element._reactRootContainer && element._reactRootContainer._internalRoot && element._reactRootContainer._internalRoot.current) || (element._reactRootContainer && element._reactRootContainer.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
getOwner(instance) {
|
getOwner(instance) {
|
||||||
|
|
184
src/utilities/dialog.js
Normal file
184
src/utilities/dialog.js
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dialog for Vue
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import {EventEmitter} from 'utilities/events';
|
||||||
|
|
||||||
|
import Site from 'site';
|
||||||
|
|
||||||
|
|
||||||
|
export default class Dialog extends EventEmitter {
|
||||||
|
constructor(element) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = null;
|
||||||
|
|
||||||
|
if ( typeof element === 'function' )
|
||||||
|
this.factory = element;
|
||||||
|
else
|
||||||
|
this.element = element;
|
||||||
|
|
||||||
|
this._visible = false;
|
||||||
|
this._maximized = false;
|
||||||
|
this._exclusive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Properties and Utility Methods
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
get maximized() {
|
||||||
|
return this._exclusive || this._maximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
set maximized(val) {
|
||||||
|
val = Boolean(val);
|
||||||
|
if ( val === this._maximized )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( this._visible )
|
||||||
|
this.toggleSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
get visible() {
|
||||||
|
return this._visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
set visible(val) {
|
||||||
|
val = Boolean(val);
|
||||||
|
if ( val === this._visible )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.toggleVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
get exclusive() {
|
||||||
|
return this._exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
set exclusive(val) {
|
||||||
|
if ( this._visible )
|
||||||
|
throw new Error('cannot set exclusive flag when dialog already visible');
|
||||||
|
|
||||||
|
this._exclusive = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
get element() {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
|
set element(val) {
|
||||||
|
if ( this._visible )
|
||||||
|
throw new Error('cannot change element when dialog already visible');
|
||||||
|
|
||||||
|
if ( !(val instanceof Node) )
|
||||||
|
throw new Error('element must be an instance of Node');
|
||||||
|
|
||||||
|
this._element = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
maximize() {
|
||||||
|
this.maximized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
restore() {
|
||||||
|
this.maximized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Element Logic
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
getContainer() {
|
||||||
|
return document.querySelector(
|
||||||
|
this._exclusive ? Dialog.EXCLUSIVE :
|
||||||
|
this._maximized ? Dialog.MAXIMIZED :
|
||||||
|
Dialog.SELECTOR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleVisible(event) {
|
||||||
|
if ( event && event.button !== 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const maximized = this.maximized,
|
||||||
|
visible = this._visible = ! this._visible,
|
||||||
|
container = this.getContainer();
|
||||||
|
|
||||||
|
if ( maximized )
|
||||||
|
container.classList.toggle('ffz-has-dialog', visible);
|
||||||
|
|
||||||
|
if ( ! visible ) {
|
||||||
|
this._element.remove();
|
||||||
|
this.emit('hide');
|
||||||
|
|
||||||
|
if ( this.factory )
|
||||||
|
this._element = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.factory ) {
|
||||||
|
const el = this.factory();
|
||||||
|
if ( el instanceof Promise ) {
|
||||||
|
el.then(e => {
|
||||||
|
this._element = e;
|
||||||
|
container.appendChild(e);
|
||||||
|
this.emit('show');
|
||||||
|
}).catch(err => {
|
||||||
|
this.emit('error', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else
|
||||||
|
this._element = el;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(this._element);
|
||||||
|
this.emit('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSize(event) {
|
||||||
|
if ( ! this._visible || event && event.button !== 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._maximized = !this._maximized;
|
||||||
|
|
||||||
|
const maximized = this.maximized,
|
||||||
|
container = this.getContainer(),
|
||||||
|
old_container = this._element.parentElement;
|
||||||
|
|
||||||
|
if ( container === old_container )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( maximized )
|
||||||
|
container.classList.add('ffz-has-dialog');
|
||||||
|
else
|
||||||
|
old_container.classList.remove('ffz-has-dialog');
|
||||||
|
|
||||||
|
this._element.remove();
|
||||||
|
container.appendChild(this._element);
|
||||||
|
|
||||||
|
this.emit('resize');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Dialog.lastZ = 99999999;
|
||||||
|
|
||||||
|
|
||||||
|
Dialog.EXCLUSIVE = Site.DIALOG_EXCLUSIVE;
|
||||||
|
Dialog.MAXIMIZED = Site.DIALOG_MAXIMIZED;
|
||||||
|
Dialog.SELECTOR = Site.DIALOG_SELECTOR;
|
|
@ -1,4 +1,4 @@
|
||||||
.ffz-main-menu {
|
.ffz-dialog {
|
||||||
&:not(.maximized) {
|
&:not(.maximized) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 25%;
|
top: 25%;
|
||||||
|
@ -62,6 +62,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.ffz-has-menu > :not(.ffz-main-menu) {
|
.ffz-has-dialog > :not(.ffz-dialog) {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
@import 'icons';
|
@import 'icons';
|
||||||
@import 'tooltips';
|
@import 'tooltips';
|
||||||
@import 'widgets';
|
@import 'widgets';
|
||||||
@import 'main_menu';
|
@import 'dialog';
|
||||||
|
|
||||||
@import 'chat';
|
@import 'chat';
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue