This file serves as a mini-documentation for a small set features in pxls.js
, for use either in either internal or external development (such as userscripts).
All components are accessible via self
internally, or App
externally. For the sake of the examples provided here, we will be using App
.
Templating is already provided via URL parameters, but is easily modifiable via scripts.
For that there is App.updateTemplate()
. It works by passing a template-object as an argument. A full template object looks like the following:
App.updateTemplate({
use: true, // boolean, whether to use the template or not
url: 'https://example.com/image.png', // string, url of image
x: 5, // float, x-position of image
y: 42, // float, y-position of image
width: 7, // float, width of image, if scaling is desired
opacity: 0.5, // float, opacity, 0 min, 1 max
});
You can omit keys if wanted:
// initialize the template
App.updateTemplate({
use: true,
url: 'https://example.com/image.png',
x: 1337,
y: 3
});
// ...
// hide the template
App.updateTemplate({ opacity: 0 });
// ...
// show the template
App.updateTemplate({ opacity: 1 });
Note that if use
is set to false
, previous parameters will be forgotten.
It's best to assign the template data to an object and supply that, or set opacity
to hide it.
We also provide storage wrappers for localStorage
(ls
) and sessionStorage
(ss
).
Both have a fallback to cookies, 99-days or session-cookies, depending on the type of storage.
It is possible to store anything that can be expressed via JSON.
App.ls.set('blah', 42); // sets 'blah' to 42
App.ls.get('blah'); // 42
App.ls.set('blah', { a: 'b' }); // sets 'blah' to the object
App.ls.get('blah'); // { a: 'b' }
App.ls.remove('blah');
App.ls.get('blah'); // undefined
It is possible to change settings using the settings object in a program-friendly way. Using the following functions will cause the changes to be reflected internally as well as update settings controls to reflect new values.
App.settings.board.heatmap.enable.set(true); // enable the heatmap
App.settings.ui.cursor.enable.toggle(); // enable/disable the cursor
App.settings.board.zoom.sensitivity.set(2); // set the zoom sensitivity
App.settings.audio.alert.volume.get(); // returns the current alert volume
The various settings keys can be found in-code or through a browser auto-complete if needed.
They will generally be named like this: component.feature.subfeature.setting
.
Also note that the .toggle()
function is only available for boolean settings (usually those ending in .enable
).
Hooks are objects that provide extra functionality for lookups.
In the example, data
supplied to get
would be lookup information, while properties in css
are assigned to their values.
App.lookup.registerHook({
id: "rebel_hook",
name: "Rebellious Hook",
get: data => `Hehehe... the X is ${data.x} and Y is ${data.y}...`,
css: {
color: "yellow",
},
});
Your function's return value can either be a jQuery object, a string (later wrapped in a span
), or null (signaling the hook shouldn't be included).
Similar to lookup hooks, hooks can also be used to intercept chat messages and highlight messages the same way a ping does.
Below is an example of a hook that would make every message a ping.
In it, data
supplied to get
would be message information.
This data contains message_raw
which is the raw text of the message as well as other values such as author
, date
and more.
App.chat.registerHook({
id: "annoying_hook",
get: data => ({
pings: [
{
start: 0,
length: data.message_raw.length,
highlight: false
}
]
})
});
Your function's return value should be an object containing an array object in the key pings
which contains one or more ping objects.
A ping object contains three keys as shown in the example: start
, length
, and highlight
.
While not currently used, these values are used to indicate if a message should highlighted as a ping and what section in it if so.
Markdown processing in chat is backed by pxls-markdown, which uses unified, remark-parse and a combination of custom plugins to handle underlines, @mentions, :emoji: 😄, etc; as well as converting the syntax tree into DOM elements.
The Processor is exposed in App.chat.markdownProcessor
. You can extend its functionality by creating a Plugins and doing App.chat.markdownProcessor.use(yourPlugin)
.
You can read more on how the processor works on unified's README or by checking the sourcecode of plugins on the pxls-markdown repository.
function myPlugin() {
const parserProto = this.Parser.prototype;
const compilerProto = this.Compiler.prototype;
/*
* Inject a new inline tokenizer into the Parser (remark-parse)
*/
// inlineMethods contains a list of parserProto.inlineTokenizers functions to run
// sequentially. This is why we add the name of our new tokenizer to the beginning, as to
// avoid any conflicts with other tokenizers.
parserProto.inlineMethods.splice(0, 0, 'myCustomMarkup');
parserProto.inlineTokenizers.myCustomMarkup = function tokenizer(eat, value, silent) {
if (value.startsWith('test')) {
if (silent) {
// If in silent mode, we are only asserting that we've found our markup.
return true;
}
// we eat "test" from the input and replace it with a Node of type "myCustomMarkup" and value "test"
return eat('test')({
type: 'myCustomMarkup',
value: 'test'
});
}
return false;
}
parserProto.inlineTokenizers.myCustomMarkup.notInLink = true;
parserProto.inlineTokenizers.myCustomMarkup.locator = function(value, fromIndex) {
// A tokenizer's locator returns the location of where our markup starts, or -1 if not found,
// for optimization purposes.
// * A constant value of -1 will make our tokenizer only run at the beginning of the source string.
// * A constant value of 0 will generate an infinite loop.
return value.indexOf('test', fromIndex);
}
/*
* Inject a new visitor into the Compiler (remark-crel)
*/
compilerProto.visitors.myCustomMarkup = (node, next) => {
// calling next() will recursively visit all childrens of our node (node.children)
// or return the value of our node (node.value) if it has no children.
const style = 'font-weight: bold; font-style: italic; text-decoration: underline;';
return crel('span', { style }, next())
};
}
App.chat.markdownProcessor.use(myPlugin);
You can listen for various custom $(window) events triggered with the pxls:
prefix. Note that these are only listen-able with jQuery.
Examples:
$(window).on('pxls:ack:place', (event, x, y) => {
console.log("Pixel succesfully placed at (" + x + ", " + y + ")");
});
$(window).on('pxls:ack:undo', (event, x, y) => {
console.log("Pixel succesfully undoed at (" + x + ", " + y + ")");
});
$(window).on('pxls:pixelCounts:update', (event, counts) => {
console.log("New pixel count: " + counts.pixelCount + " current, " + counts.pixelCountAllTime + " all time");
});
/** Events used internally but still available to anyone **/
$(window).on('pxls:panel:opened', (event, panelDescriptor) => {
console.log("Opened panel " + panelDescriptor);
});
$(window).on('pxls:panel:closed', (event, panelDescriptor) => {
console.log("Closed panel " + panelDescriptor);
});
$(window).on('pxls:queryUpdated', (event, propName, oldValue, newValue) => {
if (oldValue !== newValue) {
console.log("Query property " + propName + " changed from " + oldValue + " to " + newValue);
}
});
$(window).on("pxls:user:loginState", function(e, isLoggedIn) {
console.log(isLoggedIn ? "Client is logged in" : "Client isn't logged in");
})
We use GNU gettext for our localization format. The source files for localizations are located in the po
directory.
Localization.pot
is the template file and Localization*.po
are the localizations for the relevant language.
Copy po/Localization.pot
to po/Localization_$LC$.po
(where $LC$
is the language ISO 639-1 code).
Fill in each empty msgstr ""
with the translation of the msgid "..."
. If additional context is needed, open the relevant file(s) (denoted by #: ...
)
and search for the relevant msgid
string.
Use the compile-localizations.js
helper script. This should generate new versions of resources/Localization*.properties
.
After creating a new localization string in the code somewhere, it must be added to the .pot
and .po
files.
Use the generate-potfile.js
helper script and the following command to unify it across all .po
files:
for f in po/*.po; do msgmerge -N -U --no-wrap $f po/Localization.pot; done
The Symbols
template style (located in views/index.handlebars
and localization files between Dotted (Big, 2:2)
and "Numbers"
)
uses the reference palette configuration. Any changes to the palette will break this style. To modify it, decode the base64 string for the symbols into a .png
and open it in an image editor.
Each symbol is in a 7x7 block going from left to right, top to bottom. The color of the symbols do not matter as they will be re-mapped to the palette when applied.
The symbols can be resized so long as they remain proportional to the image size so that there are two rows of 16 blocks each, or however many colors are in the palette.
Once finished editing, convert the .png
back into base64 format and replace the option value in index.html
.
To remove the symbols, delete the <option value="...">{{i18n('Localization', 'Symbols') | raw}}</option>
from index.html
and remove or comment out the relevant
localization string from each file in po/
. Compile localizations with node compile-localizations.js
.