jspaint/index.html

695 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JS Paint</title>
<link href="styles/normalize.css" rel="stylesheet" type="text/css">
<link href="styles/layout.css" class="flippable-layout-stylesheet" rel="stylesheet" type="text/css">
<link href="styles/print.css" rel="stylesheet" type="text/css" media="print">
<link href="lib/os-gui/layout.css" class="flippable-layout-stylesheet" rel="stylesheet" type="text/css">
<!-- <link href="lib/os-gui/windows-98.css" rel="stylesheet" type="text/css"> -->
<!-- <link href="lib/os-gui/windows-default.css" rel="stylesheet" type="text/css" title="Windows Default"> -->
<!-- <link href="lib/os-gui/peggys-pastels.css" rel="alternate stylesheet" type="text/css" title="Peggy's Pastels"> -->
<link rel="apple-touch-icon" href="images/icons/apple-icon-180x180.png">
<!-- Chrome will pick the largest image for some reason, instead of the most appropriate one. -->
<!-- <link rel="icon" type="image/png" sizes="192x192" href="images/icons/192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="images/icons/32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="images/icons/96x96.png"> -->
<!-- <link rel="icon" type="image/png" sizes="16x16" href="images/icons/16x16.png"> -->
<link rel="shortcut icon" href="favicon.ico">
<link rel="mask-icon" href="images/icons/safari-pinned-tab.svg" color="red">
<link rel="manifest" href="manifest.webmanifest">
<meta name="msapplication-TileColor" content="#008080">
<meta name="msapplication-TileImage" content="images/icons/ms-icon-144x144.png">
<meta name="theme-color" content="#000080">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="description" content="Classic MS Paint in the browser, with extra features" />
<meta property="og:image:width" content="279">
<meta property="og:image:height" content="279">
<meta property="og:description" content="Classic MS Paint in the browser, with extra features.">
<meta property="og:title" content="JS Paint">
<meta property="og:url" content="https://jspaint.app">
<meta property="og:image" content="https://jspaint.app/images/icons/og-image-279x279.jpg">
<meta name="twitter:title" content="JS Paint">
<meta name="twitter:description" content="Classic MS Paint in the browser, with extra features">
<meta name="twitter:image" content="https://jspaint.app/images/meta/twitter-card-plz-no-crop.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@isaiahodhner">
<meta name="twitter:creator" content="@isaiahodhner">
<script src="src/theme.js"></script>
</head>
<body>
<div id="about-paint" style="display: none">
<h1 id="about-paint-header">
<img src="images/icons/32x32.png" width="32" height="32" id="paint-32x32" alt=""/>
<span id="jspaint-project-name">JS Paint</span>
<small id="jspaint-version" title="expect bugs!">Alpha</small>
<button id="view-project-news">View Project News</button>
</h1>
<div id="maybe-outdated-view-project-news" hidden>
<div id="maybe-outdated-line">
<div id="outdated" hidden>
<div class="on-official-host">
There's a new version of JS Paint. <a id="refresh-to-update" href=".">Refresh</a> to get it.
</div>
<div class="on-third-party-host">
This instance of JS Paint is outdated compared to <a href="https://jspaint.app" target="_blank">jspaint.app</a>.
</div>
<div class="on-dev-host">
This version of JS Paint is outdated compared to <a href="https://jspaint.app" target="_blank">jspaint.app</a>.
</div>
</div>
<div id="checking-for-updates" hidden>
Checking for updates...
</div>
<div id="failed-to-check-if-outdated" hidden>
Couldn't check for updates.
<span class="navigator-offline">You're offline.</span>
<span class="navigator-online">JS Paint may be outdated.</span>
</div>
</div>
</div>
<p>JS Paint is a web-based remake of MS Paint by <a href="https://isaiahodhner.io/">Isaiah Odhner</a>.</p>
<p>Read about the project and <b>extra features</b> on <a href="https://github.com/1j01/jspaint#readme">the README</a>.</p>
<p>Request features and report bugs <a href="https://github.com/1j01/jspaint/issues">on GitHub</a>
or <a href="mailto:isaiahodhner@gmail.com?subject=JS%20Paint">by email</a>.</p>
<p>If you want to support the project &amp; cover domain fees:
<a href="https://www.paypal.me/IsaiahOdhner" target="_blank">paypal.me/IsaiahOdhner</a>
</p>
</div>
<div id="news" hidden>
<article class="news-entry" id="news-2019-winter-update">
<h1>Winter Update</h1>
<time datetime="2019-12-20">2019-12-20</time>
<img width="563" height="334" style="max-width: 100%; height: auto;" src="https://i.postimg.cc/63Wc6vpG/2019-winter-update-candy-cane.gif"/>
<h2>Winter Theme</h2>
<p>
A new UI skin is available, under <b>Extras &gt; Themes &gt; Winter</b>, featuring winter and holiday icons, festive fonts, and a palette with seasonal colors and peppermint patterns.
</p>
<img width="256" height="16" src="images/winter/tools.png"/>
<p>
Merry Christmas and happy Hanukkah!
</p>
<h2>Better History</h2>
<b class="new">New:</b> Jump to any point in the document's history, forwards or backwards, with <b>Edit &gt; History</b> or <kbd>Ctrl+Shift+Y</kbd>.
<ul>
<li>
Click on Text in the history view to go back to text editing.
</li>
<li>
You can return to when a selection existed.
</li>
<li>
Note: these states are skipped over with normal Undo and Redo, so you need to use the History window.
</li>
<li>
Branching history: if you undo, and then make changes, you can get back to everything.
Future states are preserved.
</li>
</ul>
<b class="bad">Warning:</b> History is not saved with the autosave. Document history will be lost if you refresh the page, or close the tab, or if the tab crashes, or if you close or restart your browser, or likely if you're just on a phone and the mobile browser loses focus.
<h2>Improved Mobile Support</h2>
<p>
<b class="new">New:</b> Use two fingers to pan the view.
</p>
<p>
I recently made it easier to grab handles for resizing things.
With that, combined with multitouch panning,
JS Paint is much more useable on a phone.
</p>
<p><b class="bad">Caveat:</b> It's slow on some devices, and parts of the interface are still too small for touch.</p>
</article>
<article class="news-entry" id="news-2019-polygon-text-and-select">
<h1>Polygon, Text, and Select</h1>
<time datetime="2019-12-04">2019-12-04</time>
<p>
Handles are now way easier to drag, with extended click targets, similar to Paint from Windows 7.
It's not unreasonable to use with a touch screen now!
This applies to selections, textboxes, and the main canvas handles.
</p>
<p>
Resizing things while zoomed in is <a href="https://github.com/1j01/jspaint/issues/13#issuecomment-562247085">finally fixed</a>!
</p>
<p>
The Text tool now perfectly previews the pixels that will be placed on the canvas.
What you see is what you get!
Also it retains all browser editing behavior, like spellcheck,
using a convoluted, yet elegant overlaying strategy.
(I prototyped this <a href="https://jsfiddle.net/1j01/wnac09u3/">here</a>
and <a href="https://jsfiddle.net/1j01/qkvfjn1r/">here</a> if you're interested.)
</p>
<p>
With the fill-only option selected, the Polygon tool now previews with inverted lines, like MS Paint does.
(When you finish the polygon, the boundary of the shape matches the preview exactly,
because it actually <em>does</em> draw a stroke, just the same color as the fill.)
</p>
</article>
<article class="news-entry" id="news-2019-zoom-viewport">
<h1>Zoom To Mouse</h1>
<time datetime="2019-10-26">2019-10-26</time>
<p>
<b class="new">New:</b> The Magnifier now lets you zoom to a specific location,
showing a preview of the new viewport.
</p>
<p>
Also, when zooming out with the Magnifier,
or changing the zoom from the toolbar or menus,
the top left corner of the viewport is now kept anchored.
</p>
<p>
Also, pasting a selection will now go to the top left of the viewport,
instead of the entire document.
</p>
</article>
<article class="news-entry" id="news-2019-grid-zoom-cursors">
<h1>The Grid, Custom Zoom, and Dynamic Cursors</h1>
<time datetime="2019-10-09">2019-10-09</time>
<p>
<b class="new">New:</b> The Grid. Zoom to 4x+ and use <b>View &gt; Zoom &gt; Show Grid</b> or <kbd>Ctrl+G</kbd> to enable.
This works with browser zoom as well to provide crisp gridlines even if you zoom in with your browser.
</p>
<p>
<b class="new">New:</b> <b>View &gt; Zoom &gt; Custom Zoom</b>,
including an actually-custom numerical zoom option, unlike MS Paint.
</p>
<p>
<b class="new">New:</b> Dynamic cursors for brush and eraser,
so you now have a preview of exactly where the tool will draw.
</p>
<p>
Also, in the event that your browser clears canvases to free up memory,
you should be more likely to be able to undo to get back to a useful state.
</p>
</article>
<article class="news-entry" id="news-2019-async-clipboard">
<h1>Full Clipboard Support</h1>
<time datetime="2019-09-21">2019-09-21</time>
<p>
JS Paint now lets you copy real image data to the Clipboard, both with keyboard shortcuts and from the Edit menu.
This feature is available in Chrome 76+. Other browsers don't support it yet, as of Sep 2019.
</p>
<p>
Also: paste a URL of an image, and JS Paint will load it.
(This is an alternative to <b>File &gt; Load from URL</b>.)
</p>
</article>
<style>
#news {
background: white;
color: black;
}
.news-entry {
padding: 20px;
max-width: 563px;
}
.news-entry > h1 {
font-size: 1.3em;
margin: 0;
margin-bottom: 0.3em;
}
.news-entry > time {
font-size: 1.2em;
color: gray;
}
.news-entry > h2 {
font-size: 1.9em;
font-weight: normal;
margin: 0;
margin-bottom: 0.3em;
margin-top: 0.3em;
}
.news-entry .new {
color: green;
}
.news-entry .bad {
color: #d11a29;
}
</style>
</div>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"
></script>
<script>
window.jQuery || document.write('<script src="lib/jquery-3.4.1.min.js"><\/script>');
</script>
<script>
// @TODO: DRY hotkey helpers
// & defines accelerators (hotkeys) in menus and buttons and things, which get underlined in the UI.
// & can be escaped by doubling it, e.g. "&Taskbar && Start Menu"
function index_of_hotkey(text) {
// Returns the index of the ampersand that defines a hotkey, or -1 if not present.
// return english_text.search(/(?<!&)&(?!&|\s)/); // not enough browser support for negative lookbehind assertions
// The space here handles beginning-of-string matching and counteracts the offset for the [^&] so it acts like a negative lookbehind
return ` ${text}`.search(/[^&]&[^&\s]/);
}
function has_hotkey(text) {
return index_of_hotkey(text) !== -1;
}
function remove_hotkey(text) {
return text.replace(/\s?\(&.\)/, "").replace(/([^&]|^)&([^&\s])/, "$1$2");
}
function display_hotkey(text) {
// misnomer: using .menu-hotkey out of lazyness, instead of a more general term like .hotkey or .accelerator
return text.replace(/([^&]|^)&([^&\s])/, "$1<span class='menu-hotkey'>$2</span>").replace(/&&/g, "&");
}
// function get_hotkey(text) {
// return text[index_of_hotkey(text) + 1].toUpperCase();
// }
let localizations = {};
window.localize = (english_text, ...interpolations)=> {
function find_localization(english_text) {
const amp_index = index_of_hotkey(english_text);
if (amp_index > -1) {
const without_hotkey = remove_hotkey(english_text);
if (localizations[without_hotkey]) {
const hotkey_def = english_text.slice(amp_index, amp_index + 2);
if (localizations[without_hotkey].toUpperCase().indexOf(hotkey_def.toUpperCase()) > -1) {
return localizations[without_hotkey];
} else {
if (has_hotkey(localizations[without_hotkey])) {
// window.console && console.warn(`Localization has differing accelerator (hotkey) hint: '${localizations[without_hotkey]}' vs '${english_text}'`);
// TODO: detect differing accelerator more generally
return `${remove_hotkey(localizations[without_hotkey])} (${hotkey_def})`;
}
return `${localizations[without_hotkey]} (${hotkey_def})`;
}
}
}
if (localizations[english_text]) {
return localizations[english_text];
}
return english_text;
}
function interpolate(text, interpolations) {
for (let i = 0; i < interpolations.length; i++) {
text = text.replace(`%${i+1}`, interpolations[i]);
}
return text;
}
return interpolate(find_localization(english_text), interpolations);
}
const language_storage_key = "jspaint language";
const accepted_languages = Array.from(navigator.languages || [navigator.language || navigator.userLanguage]);
try {
if (localStorage[language_storage_key]) {
accepted_languages.unshift(localStorage[language_storage_key]);
}
} catch (error) {}
const base_language = "en";
const available_languages = ["ar", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hu", "it", "ja", "ko", "nl", "no", "pl", "pt", "pt-br", "ru", "sk", "sl", "sv", "tr", "zh", "zh-simplified"];
const language_names = {
// "639-1": [["ISO language name"], ["Native name (endonym)"]],
ab: [["Abkhazian"], ["Аҧсуа Бызшәа", "Аҧсшәа"]],
aa: [["Afar"], ["Afaraf"]],
af: [["Afrikaans"], ["Afrikaans"]],
ak: [["Akan"], ["Akan"]],
sq: [["Albanian"], ["Shqip"]],
am: [["Amharic"], ["አማርኛ"]],
ar: [["Arabic"], ["العربية"]],
an: [["Aragonese"], ["Aragonés"]],
hy: [["Armenian"], ["Հայերեն"]],
as: [["Assamese"], ["অসমীয়া"]],
av: [["Avaric"], ["Авар МацӀ", "МагӀарул МацӀ"]],
ae: [["Avestan"], ["Avesta"]],
ay: [["Aymara"], ["Aymar Aru"]],
az: [["Azerbaijani"], ["Azərbaycan Dili"]],
bm: [["Bambara"], ["Bamanankan"]],
ba: [["Bashkir"], ["Башҡорт Теле"]],
eu: [["Basque"], ["Euskara", "Euskera"]],
be: [["Belarusian"], ["Беларуская Мова"]],
bn: [["Bengali"], ["বাংলা"]],
bh: [["Bihari Languages"], ["भोजपुरी"]],
bi: [["Bislama"], ["Bislama"]],
bs: [["Bosnian"], ["Bosanski Jezik"]],
br: [["Breton"], ["Brezhoneg"]],
bg: [["Bulgarian"], ["Български Език"]],
my: [["Burmese"], ["ဗမာစာ"]],
ca: [["Catalan", "Valencian"], ["Català", "Valencià"]],
ch: [["Chamorro"], ["Chamoru"]],
ce: [["Chechen"], ["Нохчийн Мотт"]],
ny: [["Chichewa", "Chewa", "Nyanja"], ["ChiCheŵa", "Chinyanja"]],
zh: [["Chinese"], ["中文", "Zhōngwén", "汉语", "漢語"]],
"zh-traditional": [["Traditional Chinese"], ["繁体中文"]], // made-up ID, not real ISO 639-1
"zh-simplified": [["Simple Chinese"], ["简体中文"]], // made-up ID, not real ISO 639-1
cv: [["Chuvash"], ["Чӑваш Чӗлхи"]],
kw: [["Cornish"], ["Kernewek"]],
co: [["Corsican"], ["Corsu", "Lingua Corsa"]],
cr: [["Cree"], ["ᓀᐦᐃᔭᐍᐏᐣ"]],
hr: [["Croatian"], ["Hrvatski Jezik"]],
cs: [["Czech"], ["Čeština", "Český Jazyk"]],
da: [["Danish"], ["Dansk"]],
dv: [["Divehi", "Dhivehi", "Maldivian"], ["ދިވެހި"]],
nl: [["Dutch", "Flemish"], ["Nederlands", "Vlaams"]],
dz: [["Dzongkha"], ["རྫོང་ཁ"]],
en: [["English"], ["English"]],
eo: [["Esperanto"], ["Esperanto"]],
et: [["Estonian"], ["Eesti", "Eesti Keel"]],
ee: [["Ewe"], ["Eʋegbe"]],
fo: [["Faroese"], ["Føroyskt"]],
fj: [["Fijian"], ["Vosa Vakaviti"]],
fi: [["Finnish"], ["Suomi", "Suomen Kieli"]],
fr: [["French"], ["Français", "Langue Française"]],
ff: [["Fulah"], ["Fulfulde", "Pulaar", "Pular"]],
gl: [["Galician"], ["Galego"]],
ka: [["Georgian"], ["ქართული"]],
de: [["German"], ["Deutsch"]],
el: [["Greek"], ["Ελληνικά"]],
gn: [["Guarani"], ["Avañe'ẽ"]],
gu: [["Gujarati"], ["ગુજરાતી"]],
ht: [["Haitian", "Haitian Creole"], ["Kreyòl Ayisyen"]],
ha: [["Hausa"], ["هَوُسَ"]],
he: [["Hebrew"], ["עברית"]],
hz: [["Herero"], ["Otjiherero"]],
hi: [["Hindi"], ["हिन्दी", "हिंदी"]],
ho: [["Hiri Motu"], ["Hiri Motu"]],
hu: [["Hungarian"], ["Magyar"]],
ia: [["Interlingua"], ["Interlingua"]],
id: [["Indonesian"], ["Bahasa Indonesia"]],
ie: [["Interlingue", "Occidental"], ["Interlingue", "Occidental"]],
ga: [["Irish"], ["Gaeilge"]],
ig: [["Igbo"], ["Asụsụ Igbo"]],
ik: [["Inupiaq"], ["Iñupiaq", "Iñupiatun"]],
io: [["Ido"], ["Ido"]],
is: [["Icelandic"], ["Íslenska"]],
it: [["Italian"], ["Italiano"]],
iu: [["Inuktitut"], ["ᐃᓄᒃᑎᑐᑦ"]],
ja: [["Japanese"], ["日本語", "にほんご"]],
jv: [["Javanese"], ["ꦧꦱꦗꦮ", "Basa Jawa"]],
kl: [["Kalaallisut", "Greenlandic"], ["Kalaallisut", "Kalaallit Oqaasii"]],
kn: [["Kannada"], ["ಕನ್ನಡ"]],
kr: [["Kanuri"], ["Kanuri"]],
ks: [["Kashmiri"], ["कश्मीरी", "كشميري‎"]],
kk: [["Kazakh"], ["Қазақ Тілі"]],
km: [["Central Khmer"], ["ខ្មែរ", "ខេមរភាសា", "ភាសាខ្មែរ"]],
ki: [["Kikuyu", "Gikuyu"], ["Gĩkũyũ"]],
rw: [["Kinyarwanda"], ["Ikinyarwanda"]],
ky: [["Kirghiz", "Kyrgyz"], ["Кыргызча", "Кыргыз Тили"]],
kv: [["Komi"], ["Коми Кыв"]],
kg: [["Kongo"], ["Kikongo"]],
ko: [["Korean"], ["한국어"]],
ku: [["Kurdish"], ["Kurdî", "کوردی‎"]],
kj: [["Kuanyama", "Kwanyama"], ["Kuanyama"]],
la: [["Latin"], ["Latine", "Lingua Latina"]],
lb: [["Luxembourgish", "Letzeburgesch"], ["Lëtzebuergesch"]],
lg: [["Ganda"], ["Luganda"]],
li: [["Limburgan", "Limburger", "Limburgish"], ["Limburgs"]],
ln: [["Lingala"], ["Lingála"]],
lo: [["Lao"], ["ພາສາລາວ"]],
lt: [["Lithuanian"], ["Lietuvių Kalba"]],
lu: [["Luba-Katanga"], ["Kiluba"]],
lv: [["Latvian"], ["Latviešu Valoda"]],
gv: [["Manx"], ["Gaelg", "Gailck"]],
mk: [["Macedonian"], ["Македонски Јазик"]],
mg: [["Malagasy"], ["Fiteny Malagasy"]],
ms: [["Malay"], ["Bahasa Melayu", "بهاس ملايو‎"]],
ml: [["Malayalam"], ["മലയാളം"]],
mt: [["Maltese"], ["Malti"]],
mi: [["Maori"], ["Te Reo Māori"]],
mr: [["Marathi"], ["मराठी"]],
mh: [["Marshallese"], ["Kajin M̧ajeļ"]],
mn: [["Mongolian"], ["Монгол Хэл"]],
na: [["Nauru"], ["Dorerin Naoero"]],
nv: [["Navajo", "Navaho"], ["Diné Bizaad"]],
nd: [["North Ndebele"], ["IsiNdebele"]],
ne: [["Nepali"], ["नेपाली"]],
ng: [["Ndonga"], ["Owambo"]],
nb: [["Norwegian Bokmål"], ["Norsk Bokmål"]],
nn: [["Norwegian Nynorsk"], ["Norsk Nynorsk"]],
no: [["Norwegian"], ["Norsk"]],
ii: [["Sichuan Yi", "Nuosu"], ["ꆈꌠ꒿", "Nuosuhxop"]],
nr: [["South Ndebele"], ["IsiNdebele"]],
oc: [["Occitan"], ["Occitan", "Lenga d'Òc"]],
oj: [["Ojibwa"], ["ᐊᓂᔑᓈᐯᒧᐎᓐ"]],
cu: [["Church Slavic", "Old Slavonic", "Church Slavonic", "Old Bulgarian", "Old Church Slavonic"], ["Ѩзыкъ Словѣньскъ"]],
om: [["Oromo"], ["Afaan Oromoo"]],
or: [["Oriya"], ["ଓଡ଼ିଆ"]],
os: [["Ossetian", "Ossetic"], ["Ирон Æвзаг"]],
pa: [["Punjabi", "Panjabi"], ["ਪੰਜਾਬੀ", "پنجابی‎"]],
pi: [["Pali"], ["पालि", "पाळि"]],
fa: [["Persian"], ["فارسی"]],
pl: [["Polish"], ["Język Polski", "Polszczyzna"]],
ps: [["Pashto", "Pushto"], ["پښتو"]],
pt: [["Portuguese"], ["Português"]],
"pt-br": [["Brazilian Portuguese"], ["Português Brasileiro"]],
"pt-pt": [["Portuguese (Portugal)"], ["Português De Portugal"]],
qu: [["Quechua"], ["Runa Simi", "Kichwa"]],
rm: [["Romansh"], ["Rumantsch Grischun"]],
rn: [["Rundi"], ["Ikirundi"]],
ro: [["Romanian", "Moldavian", "Moldovan"], ["Română"]],
ru: [["Russian"], ["Русский"]],
sa: [["Sanskrit"], ["संस्कृतम्"]],
sc: [["Sardinian"], ["Sardu"]],
sd: [["Sindhi"], ["सिन्धी", "سنڌي، سندھی‎"]],
se: [["Northern Sami"], ["Davvisámegiella"]],
sm: [["Samoan"], ["Gagana Fa'a Samoa"]],
sg: [["Sango"], ["Yângâ Tî Sängö"]],
sr: [["Serbian"], ["Српски Језик"]],
gd: [["Gaelic", "Scottish Gaelic"], ["Gàidhlig"]],
sn: [["Shona"], ["ChiShona"]],
si: [["Sinhala", "Sinhalese"], ["සිංහල"]],
sk: [["Slovak"], ["Slovenčina", "Slovenský Jazyk"]],
sl: [["Slovenian"], ["Slovenski Jezik", "Slovenščina"]],
so: [["Somali"], ["Soomaaliga", "Af Soomaali"]],
st: [["Southern Sotho"], ["Sesotho"]],
es: [["Spanish", "Castilian"], ["Español"]],
su: [["Sundanese"], ["Basa Sunda"]],
sw: [["Swahili"], ["Kiswahili"]],
ss: [["Swati"], ["SiSwati"]],
sv: [["Swedish"], ["Svenska"]],
ta: [["Tamil"], ["தமிழ்"]],
te: [["Telugu"], ["తెలుగు"]],
tg: [["Tajik"], ["Тоҷикӣ", "Toçikī", "تاجیکی‎"]],
th: [["Thai"], ["ไทย"]],
ti: [["Tigrinya"], ["ትግርኛ"]],
bo: [["Tibetan"], ["བོད་ཡིག"]],
tk: [["Turkmen"], ["Türkmen", "Түркмен"]],
tl: [["Tagalog"], ["Wikang Tagalog"]],
tn: [["Tswana"], ["Setswana"]],
to: [["Tonga"], ["Faka Tonga"]],
tr: [["Turkish"], ["Türkçe"]],
ts: [["Tsonga"], ["Xitsonga"]],
tt: [["Tatar"], ["Татар Теле", "Tatar Tele"]],
tw: [["Twi"], ["Twi"]],
ty: [["Tahitian"], ["Reo Tahiti"]],
ug: [["Uighur", "Uyghur"], ["ئۇيغۇرچە‎", "Uyghurche"]],
uk: [["Ukrainian"], ["Українська"]],
ur: [["Urdu"], ["اردو"]],
uz: [["Uzbek"], ["Oʻzbek", "Ўзбек", "أۇزبېك‎"]],
ve: [["Venda"], ["Tshivenḓa"]],
vi: [["Vietnamese"], ["Tiếng Việt"]],
vo: [["Volapük"], ["Volapük"]],
wa: [["Walloon"], ["Walon"]],
cy: [["Welsh"], ["Cymraeg"]],
wo: [["Wolof"], ["Wollof"]],
fy: [["Western Frisian"], ["Frysk"]],
xh: [["Xhosa"], ["IsiXhosa"]],
yi: [["Yiddish"], ["ייִדיש"]],
yo: [["Yoruba"], ["Yorùbá"]],
za: [["Zhuang", "Chuang"], ["Saɯ Cueŋƅ", "Saw Cuengh"]],
zu: [["Zulu"], ["IsiZulu"]],
};
function get_iso_language_name(language) {
return language_names[language][0][0];
}
function get_endonym(language) {
return language_names[language][1][0];
}
let current_language = base_language;
for (const accepted_language of accepted_languages) {
if (available_languages.indexOf(accepted_language) !== -1) {
current_language = accepted_language;
break;
}
}
function get_language() {
return current_language;
}
function get_direction(language=current_language) {
return current_language.match(/^(ar|dv|fa|ha|he|ks|ku|ms|pa|ps|sd|ug|yi)\b/i) ? "rtl" : "ltr";
}
function load_language(language) {
const prev_language = current_language;
const stylesheets = [...document.querySelectorAll(".flippable-layout-stylesheet")];
for (const stylesheet of stylesheets) {
let href = stylesheet.getAttribute("href");
if (get_direction(language) === "rtl") {
if (href.indexOf(".rtl.css") === -1) {
href = href.replace(/\.css/i, ".rtl.css");
}
} else {
if (href.indexOf(".rtl.css") > -1) {
href = href.replace(/\.rtl\.css/i, ".css");
}
}
stylesheet.setAttribute("href", href);
// hack to wait for stylesheet to load
const img = document.createElement("img");
img.onerror = ()=> {
$(()=> {
$G.triggerHandler("theme-load"); // signal layout change
});
};
img.src = href;
}
if (language === base_language) {
localizations = {};
current_language = base_language;
return;
}
// fetch(`localization/${language}/localizations.json`)
// .then((response)=> response.json())
// .then((new_localizations)=> {
// localizations = new_localizations;
// current_language = language;
// }).catch((error)=> {
// show_error_message(`Failed to load localizations for ${language_names[language]}.`, error);
// current_language = prev_language;
// });
const src = `localization/${language}/localizations.js`;
document.write(`<script src="${src}"><\/script>`);
}
// JSONP callback
function loaded_localizations(language, mapping) {
localizations = mapping;
current_language = language;
}
function set_language(language) {
const $w = $FormToolWindow().title("Reload Required").addClass("dialogue-window");
$w.$main.text("The application needs to reload to change the language.");
$w.$main.css("max-width", "600px");
$w.$Button(localize("OK"), () => {
$w.close();
are_you_sure(() => {
try {
localStorage[language_storage_key] = language;
location.reload();
} catch(error) {
show_error_message("Failed to store language preference. Make sure cookies / local storage is enabled in your browser settings.", error);
}
});
});
$w.$Button(localize("Cancel"), () => {
$w.close();
});
$w.center();
// load_language(language);
}
load_language(current_language);
</script>
<script src="lib/pep.js"></script>
<script src="lib/canvas.toBlob.js"></script>
<script src="lib/gif.js/gif.js"></script>
<script src="lib/palette.js"></script>
<script src="lib/FileSaver.js"></script>
<script src="lib/font-detective.js"></script>
<script src="lib/libtess.min.js"></script>
<script src="lib/os-gui/parse-theme.js"></script>
<script src="lib/os-gui/$Window.js"></script>
<script src="lib/os-gui/$MenuBar.js"></script>
<script src="lib/imagetracer_v1.2.5.js"></script>
<script src="src/helpers.js"></script>
<script src="src/storage.js"></script>
<script src="src/$Component.js"></script>
<script src="src/$ToolWindow.js"></script>
<script src="src/$ToolBox.js"></script>
<script src="src/$ColorBox.js"></script>
<script src="src/$FontBox.js"></script>
<script src="src/$Handles.js"></script>
<script src="src/OnCanvasObject.js"></script>
<script src="src/OnCanvasSelection.js"></script>
<script src="src/OnCanvasTextBox.js"></script>
<script src="src/OnCanvasHelperLayer.js"></script>
<script src="src/image-manipulation.js"></script>
<script src="src/tool-options.js"></script>
<script src="src/tools.js"></script>
<!--<script src="src/extra-tools.js"></script>-->
<script src="src/edit-colors.js"></script>
<script src="src/functions.js"></script>
<script src="src/manage-storage.js"></script>
<script src="src/imgur.js"></script>
<script src="src/help.js"></script>
<script src="src/simulate-random-gestures.js"></script>
<script src="src/menus.js"></script>
<script src="src/speech-recognition.js"></script>
<script src="src/app.js"></script>
<script src="src/sessions.js"></script>
<script src="lib/konami.js"></script>
<script src="src/vaporwave-fun.js"></script>
<script>
if(/MSIE \d|Trident.*rv:/.test(navigator.userAgent)){
document.write(
'<style>body { text-align: center; }</style>' +
'<div className="not-supported">' +
'<h1 className="not-supported-header">Internet Explorer is not supported!</h1>' +
'<p className="not-supported-details">Try Chrome, Firefox, or Edge.</p>' +
'</div>'
);
}
</script>
<noscript>
<h1><img src="images/icons/32x32.png" width="32" height="32" alt=""/> JS Paint</h1>
<p>This application requires JavaScript to run.</p>
<p>
Assuming this is the official instance of jspaint,
at <a href="https://jspaint.app">https://jspaint.app</a>,
you can safely enable JavaScript.
</p>
<p>You can also check out <a href="https://github.com/1j01/jspaint">the source code and project info</a>.</p>
</noscript>
<svg style="position: absolute; pointer-events: none; bottom: 100%;">
<defs>
<filter id="disabled-inset-filter" x="0" y="0" width="1px" height="1px">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
-1000 -1000 -1000 1 0
"
result="black-parts-isolated"
/>
<feFlood result="shadow-color" flood-color="var(--ButtonShadow)"/>
<feFlood result="hilight-color" flood-color="var(--ButtonHilight)"/>
<feOffset in="black-parts-isolated" dx="1" dy="1" result="offset"/>
<feComposite in="hilight-color" in2="offset" operator="in" result="hilight-colored-offset"/>
<feComposite in="shadow-color" in2="black-parts-isolated" operator="in" result="shadow-colored"/>
<feMerge>
<feMergeNode in="hilight-colored-offset"/>
<feMergeNode in="shadow-colored"/>
</feMerge>
</filter>
</defs>
</svg>
</body>
</html>