mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-24 19:59:36 +00:00
Merge pull request #2 from IdreesInc/rollup
Implement Rollup and split up code
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/node_modules
|
||||
.DS_Store
|
||||
/dist/birb.bundled.js
|
||||
|
||||
26
build.js
26
build.js
@@ -1,6 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { rollup } from 'rollup';
|
||||
import { readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
|
||||
|
||||
const spriteSheets = [
|
||||
{
|
||||
@@ -10,14 +11,10 @@ const spriteSheets = [
|
||||
{
|
||||
key: "__FEATHER_SPRITE_SHEET__",
|
||||
path: "./sprites/feather.png"
|
||||
},
|
||||
{
|
||||
key: "__DECORATIONS_SPRITE_SHEET__",
|
||||
path: "./sprites/decorations.png"
|
||||
}
|
||||
];
|
||||
|
||||
const STYLESHEET_PATH = "./stylesheet.css";
|
||||
const STYLESHEET_PATH = "./src/stylesheet.css";
|
||||
const STYLESHEET_KEY = "___STYLESHEET___";
|
||||
|
||||
const now = new Date();
|
||||
@@ -52,7 +49,6 @@ try {
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
const userScriptHeader =
|
||||
`// ==UserScript==
|
||||
// @name Pocket Bird
|
||||
@@ -70,8 +66,19 @@ const userScriptHeader =
|
||||
|
||||
`;
|
||||
|
||||
// Bundle with rollup
|
||||
const bundle = await rollup({
|
||||
input: 'src/application.js',
|
||||
});
|
||||
|
||||
let birbJs = readFileSync('birb.js', 'utf8');
|
||||
await bundle.write({
|
||||
file: 'dist/birb.bundled.js',
|
||||
format: 'iife',
|
||||
});
|
||||
|
||||
await bundle.close();
|
||||
|
||||
let birbJs = readFileSync('dist/birb.bundled.js', 'utf8');
|
||||
|
||||
// Compile and insert sprite sheets
|
||||
for (const spriteSheet of spriteSheets) {
|
||||
@@ -86,6 +93,9 @@ birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent);
|
||||
// Build standard javascript file
|
||||
writeFileSync('./dist/birb.js', birbJs);
|
||||
|
||||
// Delete bundled file
|
||||
unlinkSync('./dist/birb.bundled.js');
|
||||
|
||||
// Build user script
|
||||
const userScript = userScriptHeader + birbJs;
|
||||
writeFileSync('./dist/birb.user.js', userScript);
|
||||
3678
dist/birb.js
vendored
3678
dist/birb.js
vendored
File diff suppressed because it is too large
Load Diff
3680
dist/birb.user.js
vendored
3680
dist/birb.user.js
vendored
File diff suppressed because it is too large
Load Diff
7
jsconfig.json
Normal file
7
jsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"target": "es2017"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "Pocket Bird",
|
||||
"description": "It's a bird, in your browser. What more could you want?",
|
||||
"version": "2025.10.25.130",
|
||||
"version": "2025.10.26.402",
|
||||
"homepage_url": "https://idreesinc.com",
|
||||
"content_scripts": [
|
||||
{
|
||||
|
||||
360
package-lock.json
generated
360
package-lock.json
generated
@@ -9,9 +9,325 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.10"
|
||||
"nodemon": "^3.1.10",
|
||||
"rollup": "^4.52.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
|
||||
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
|
||||
"integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
|
||||
"integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
|
||||
"integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
|
||||
"integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
|
||||
"integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
|
||||
"integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
|
||||
"integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
|
||||
"integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
|
||||
"integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
|
||||
"integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
|
||||
"integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
|
||||
"integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
@@ -316,6 +632,48 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.5",
|
||||
"@rollup/rollup-android-arm64": "4.52.5",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.5",
|
||||
"@rollup/rollup-darwin-x64": "4.52.5",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.5",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.5",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.5",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.5",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.5",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.5",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.5",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.5",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.5",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.5",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.5",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.5",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node build.js",
|
||||
"dev": "nodemon --watch birb.js --watch stylesheet.css --watch build.js --exec \"npm run build\""
|
||||
"dev": "nodemon --watch src --watch stylesheet.css --watch build.js --exec \"npm run build\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.10"
|
||||
"nodemon": "^3.1.10",
|
||||
"rollup": "^4.52.5"
|
||||
}
|
||||
}
|
||||
|
||||
73
src/Frame.js
Normal file
73
src/Frame.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Directions } from './shared.js';
|
||||
import { SPRITE, BirdType } from './sprites.js';
|
||||
import Layer from './layer.js';
|
||||
|
||||
class Frame {
|
||||
|
||||
/** @type {{ [tag: string]: string[][] }} */
|
||||
#pixelsByTag = {};
|
||||
|
||||
/**
|
||||
* @param {Layer[]} layers
|
||||
*/
|
||||
constructor(layers) {
|
||||
/** @type {Set<string>} */
|
||||
let tags = new Set();
|
||||
for (let layer of layers) {
|
||||
tags.add(layer.tag);
|
||||
}
|
||||
tags.add("default");
|
||||
for (let tag of tags) {
|
||||
let maxHeight = layers.reduce((max, layer) => Math.max(max, layer.pixels.length), 0);
|
||||
if (layers[0].tag !== "default") {
|
||||
throw new Error("First layer must have the 'default' tag");
|
||||
}
|
||||
this.pixels = layers[0].pixels.map(row => row.slice());
|
||||
// Pad from top with transparent pixels
|
||||
while (this.pixels.length < maxHeight) {
|
||||
this.pixels.unshift(new Array(this.pixels[0].length).fill(SPRITE.TRANSPARENT));
|
||||
}
|
||||
// Combine layers
|
||||
for (let i = 1; i < layers.length; i++) {
|
||||
if (layers[i].tag === "default" || layers[i].tag === tag) {
|
||||
let layerPixels = layers[i].pixels;
|
||||
let topMargin = maxHeight - layerPixels.length;
|
||||
for (let y = 0; y < layerPixels.length; y++) {
|
||||
for (let x = 0; x < layerPixels[y].length; x++) {
|
||||
this.pixels[y + topMargin][x] = layerPixels[y][x] !== SPRITE.TRANSPARENT ? layerPixels[y][x] : this.pixels[y + topMargin][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#pixelsByTag[tag] = this.pixels.map(row => row.slice());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [tag]
|
||||
* @returns {string[][]}
|
||||
*/
|
||||
getPixels(tag = "default") {
|
||||
return this.#pixelsByTag[tag] ?? this.#pixelsByTag["default"];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {BirdType} [species]
|
||||
* @param {number} direction
|
||||
* @param {number} canvasPixelSize
|
||||
*/
|
||||
draw(ctx, direction, canvasPixelSize, species) {
|
||||
const pixels = this.getPixels(species?.tags[0]);
|
||||
for (let y = 0; y < pixels.length; y++) {
|
||||
const row = pixels[y];
|
||||
for (let x = 0; x < pixels[y].length; x++) {
|
||||
const cell = direction === Directions.LEFT ? row[x] : row[pixels[y].length - x - 1];
|
||||
ctx.fillStyle = species?.colors[cell] ?? cell;
|
||||
ctx.fillRect(x * canvasPixelSize, y * canvasPixelSize, canvasPixelSize, canvasPixelSize);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Frame;
|
||||
12
src/Layer.js
Normal file
12
src/Layer.js
Normal file
@@ -0,0 +1,12 @@
|
||||
class Layer {
|
||||
/**
|
||||
* @param {string[][]} pixels
|
||||
* @param {string} [tag]
|
||||
*/
|
||||
constructor(pixels, tag = "default") {
|
||||
this.pixels = pixels;
|
||||
this.tag = tag;
|
||||
}
|
||||
}
|
||||
|
||||
export default Layer;
|
||||
48
src/anim.js
Normal file
48
src/anim.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import Frame from "./frame.js";
|
||||
import { BirdType } from "./sprites";
|
||||
|
||||
class Anim {
|
||||
/**
|
||||
* @param {Frame[]} frames
|
||||
* @param {number[]} durations
|
||||
* @param {boolean} loop
|
||||
*/
|
||||
constructor(frames, durations, loop = true) {
|
||||
this.frames = frames;
|
||||
this.durations = durations;
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
getAnimationDuration() {
|
||||
return this.durations.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} direction
|
||||
* @param {number} timeStart The start time of the animation in milliseconds
|
||||
* @param {number} canvasPixelSize The size of a canvas pixel in pixels
|
||||
* @param {BirdType} [species] The species to use for the animation
|
||||
* @returns {boolean} Whether the animation is complete
|
||||
*/
|
||||
draw(ctx, direction, timeStart, canvasPixelSize, species) {
|
||||
let time = Date.now() - timeStart;
|
||||
const duration = this.getAnimationDuration();
|
||||
if (this.loop) {
|
||||
time %= duration;
|
||||
}
|
||||
let totalDuration = 0;
|
||||
for (let i = 0; i < this.durations.length; i++) {
|
||||
totalDuration += this.durations[i];
|
||||
if (time < totalDuration) {
|
||||
this.frames[i].draw(ctx, direction, canvasPixelSize, species);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Draw the last frame if the animation is complete
|
||||
this.frames[this.frames.length - 1].draw(ctx, direction, canvasPixelSize, species);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default Anim;
|
||||
1009
src/application.js
Normal file
1009
src/application.js
Normal file
File diff suppressed because it is too large
Load Diff
268
src/birb.js
Normal file
268
src/birb.js
Normal file
@@ -0,0 +1,268 @@
|
||||
import { Directions, getLayer } from './shared.js';
|
||||
import Layer from './layer.js';
|
||||
import Frame from './frame.js';
|
||||
import Anim from './anim.js';
|
||||
import { BirdType } from './sprites.js';
|
||||
|
||||
/**
|
||||
* @typedef {keyof typeof Animations} AnimationType
|
||||
*/
|
||||
|
||||
export const Animations = /** @type {const} */ ({
|
||||
STILL: "STILL",
|
||||
BOB: "BOB",
|
||||
FLYING: "FLYING",
|
||||
HEART: "HEART"
|
||||
});
|
||||
|
||||
export class Birb {
|
||||
animStart = Date.now();
|
||||
x = 0;
|
||||
y = 0;
|
||||
direction = Directions.RIGHT;
|
||||
isAbsolutePositioned = false;
|
||||
visible = true;
|
||||
/** @type {AnimationType} */
|
||||
currentAnimation = Animations.STILL;
|
||||
|
||||
/**
|
||||
* @param {number} birbCssScale
|
||||
* @param {number} canvasPixelSize
|
||||
* @param {string[][]} spriteSheet The loaded sprite sheet pixel data
|
||||
* @param {number} spriteWidth
|
||||
* @param {number} spriteHeight
|
||||
*/
|
||||
constructor(birbCssScale, canvasPixelSize, spriteSheet, spriteWidth, spriteHeight) {
|
||||
this.birbCssScale = birbCssScale;
|
||||
this.canvasPixelSize = canvasPixelSize;
|
||||
this.windowPixelSize = canvasPixelSize * birbCssScale;
|
||||
this.spriteWidth = spriteWidth;
|
||||
this.spriteHeight = spriteHeight;
|
||||
|
||||
// Build layers from sprite sheet
|
||||
this.layers = {
|
||||
base: new Layer(getLayer(spriteSheet, 0, this.spriteWidth)),
|
||||
down: new Layer(getLayer(spriteSheet, 1, this.spriteWidth)),
|
||||
heartOne: new Layer(getLayer(spriteSheet, 2, this.spriteWidth)),
|
||||
heartTwo: new Layer(getLayer(spriteSheet, 3, this.spriteWidth)),
|
||||
heartThree: new Layer(getLayer(spriteSheet, 4, this.spriteWidth)),
|
||||
tuftBase: new Layer(getLayer(spriteSheet, 5, this.spriteWidth), "tuft"),
|
||||
tuftDown: new Layer(getLayer(spriteSheet, 6, this.spriteWidth), "tuft"),
|
||||
wingsUp: new Layer(getLayer(spriteSheet, 7, this.spriteWidth)),
|
||||
wingsDown: new Layer(getLayer(spriteSheet, 8, this.spriteWidth)),
|
||||
happyEye: new Layer(getLayer(spriteSheet, 9, this.spriteWidth)),
|
||||
};
|
||||
|
||||
// Build frames from layers
|
||||
this.frames = {
|
||||
base: new Frame([this.layers.base, this.layers.tuftBase]),
|
||||
headDown: new Frame([this.layers.down, this.layers.tuftDown]),
|
||||
wingsDown: new Frame([this.layers.base, this.layers.tuftBase, this.layers.wingsDown]),
|
||||
wingsUp: new Frame([this.layers.down, this.layers.tuftDown, this.layers.wingsUp]),
|
||||
heartOne: new Frame([this.layers.base, this.layers.tuftBase, this.layers.happyEye, this.layers.heartOne]),
|
||||
heartTwo: new Frame([this.layers.base, this.layers.tuftBase, this.layers.happyEye, this.layers.heartTwo]),
|
||||
heartThree: new Frame([this.layers.base, this.layers.tuftBase, this.layers.happyEye, this.layers.heartThree]),
|
||||
heartFour: new Frame([this.layers.base, this.layers.tuftBase, this.layers.happyEye, this.layers.heartTwo]),
|
||||
};
|
||||
|
||||
// Build animations from frames
|
||||
this.animations = {
|
||||
[Animations.STILL]: new Anim([this.frames.base], [1000]),
|
||||
[Animations.BOB]: new Anim([
|
||||
this.frames.base,
|
||||
this.frames.headDown
|
||||
], [
|
||||
420,
|
||||
420
|
||||
]),
|
||||
[Animations.FLYING]: new Anim([
|
||||
this.frames.base,
|
||||
this.frames.wingsUp,
|
||||
this.frames.headDown,
|
||||
this.frames.wingsDown,
|
||||
], [
|
||||
30,
|
||||
80,
|
||||
30,
|
||||
60,
|
||||
]),
|
||||
[Animations.HEART]: new Anim([
|
||||
this.frames.heartOne,
|
||||
this.frames.heartTwo,
|
||||
this.frames.heartThree,
|
||||
this.frames.heartFour,
|
||||
this.frames.heartThree,
|
||||
this.frames.heartFour,
|
||||
this.frames.heartThree,
|
||||
this.frames.heartFour,
|
||||
], [
|
||||
60,
|
||||
80,
|
||||
250,
|
||||
250,
|
||||
250,
|
||||
250,
|
||||
250,
|
||||
250,
|
||||
], false),
|
||||
};
|
||||
|
||||
// Create canvas element
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.canvas.id = "birb";
|
||||
this.canvas.width = this.frames.base.getPixels()[0].length * canvasPixelSize;
|
||||
this.canvas.height = spriteHeight * canvasPixelSize;
|
||||
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
|
||||
// Append to document
|
||||
document.body.appendChild(this.canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the current animation frame
|
||||
* @param {BirdType} species The species color data
|
||||
* @returns {boolean} Whether the animation has completed (for non-looping animations)
|
||||
*/
|
||||
draw(species) {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
const anim = this.animations[this.currentAnimation];
|
||||
return anim.draw(this.ctx, this.direction, this.animStart, this.canvasPixelSize, species);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {AnimationType} The current animation key
|
||||
*/
|
||||
getCurrentAnimation() {
|
||||
return this.currentAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current animation by name and reset the animation timer
|
||||
* @param {AnimationType} animationName
|
||||
*/
|
||||
setAnimation(animationName) {
|
||||
this.currentAnimation = animationName;
|
||||
this.animStart = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frames object
|
||||
* @returns {Record<string, Frame>}
|
||||
*/
|
||||
getFrames() {
|
||||
return this.frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canvas element
|
||||
* @returns {HTMLCanvasElement}
|
||||
*/
|
||||
getElement() {
|
||||
return this.canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canvas width in CSS pixels
|
||||
* @returns {number}
|
||||
*/
|
||||
getElementWidth() {
|
||||
return this.canvas.width * this.birbCssScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canvas height in CSS pixels
|
||||
* @returns {number}
|
||||
*/
|
||||
getElementHeight() {
|
||||
return this.canvas.height * this.birbCssScale;
|
||||
}
|
||||
|
||||
getElementTop() {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
return rect.top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the X position
|
||||
* @param {number} x
|
||||
*/
|
||||
setX(x) {
|
||||
this.x = x;
|
||||
let mod = this.getElementWidth() / -2 - (this.windowPixelSize * (this.direction === Directions.RIGHT ? 2 : -2));
|
||||
this.canvas.style.left = `${x + mod}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Y position
|
||||
* @param {number} y
|
||||
*/
|
||||
setY(y) {
|
||||
this.y = y;
|
||||
let bottom;
|
||||
if (this.isAbsolutePositioned) {
|
||||
// Position is absolute, convert from fixed
|
||||
bottom = y - window.scrollY;
|
||||
} else {
|
||||
// Position is fixed
|
||||
bottom = y;
|
||||
}
|
||||
this.canvas.style.bottom = `${bottom}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current X position
|
||||
* @returns {number}
|
||||
*/
|
||||
getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Y position
|
||||
* @returns {number}
|
||||
*/
|
||||
getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the direction the bird is facing
|
||||
* @param {number} direction
|
||||
*/
|
||||
setDirection(direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the element should be absolutely positioned
|
||||
* @param {boolean} absolute
|
||||
*/
|
||||
setAbsolutePositioned(absolute) {
|
||||
this.isAbsolutePositioned = absolute;
|
||||
if (absolute) {
|
||||
this.canvas.classList.add("birb-absolute");
|
||||
} else {
|
||||
this.canvas.classList.remove("birb-absolute");
|
||||
}
|
||||
// Update Y position to apply the new positioning mode
|
||||
this.setY(this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visibility of the bird
|
||||
* @param {boolean} visible
|
||||
*/
|
||||
setVisible(visible) {
|
||||
this.visible = visible;
|
||||
this.canvas.style.display = visible ? "" : "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visibility of the bird
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
}
|
||||
73
src/frame.js
Normal file
73
src/frame.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Directions } from './shared.js';
|
||||
import { SPRITE, BirdType } from './sprites.js';
|
||||
import Layer from './layer.js';
|
||||
|
||||
class Frame {
|
||||
|
||||
/** @type {{ [tag: string]: string[][] }} */
|
||||
#pixelsByTag = {};
|
||||
|
||||
/**
|
||||
* @param {Layer[]} layers
|
||||
*/
|
||||
constructor(layers) {
|
||||
/** @type {Set<string>} */
|
||||
let tags = new Set();
|
||||
for (let layer of layers) {
|
||||
tags.add(layer.tag);
|
||||
}
|
||||
tags.add("default");
|
||||
for (let tag of tags) {
|
||||
let maxHeight = layers.reduce((max, layer) => Math.max(max, layer.pixels.length), 0);
|
||||
if (layers[0].tag !== "default") {
|
||||
throw new Error("First layer must have the 'default' tag");
|
||||
}
|
||||
this.pixels = layers[0].pixels.map(row => row.slice());
|
||||
// Pad from top with transparent pixels
|
||||
while (this.pixels.length < maxHeight) {
|
||||
this.pixels.unshift(new Array(this.pixels[0].length).fill(SPRITE.TRANSPARENT));
|
||||
}
|
||||
// Combine layers
|
||||
for (let i = 1; i < layers.length; i++) {
|
||||
if (layers[i].tag === "default" || layers[i].tag === tag) {
|
||||
let layerPixels = layers[i].pixels;
|
||||
let topMargin = maxHeight - layerPixels.length;
|
||||
for (let y = 0; y < layerPixels.length; y++) {
|
||||
for (let x = 0; x < layerPixels[y].length; x++) {
|
||||
this.pixels[y + topMargin][x] = layerPixels[y][x] !== SPRITE.TRANSPARENT ? layerPixels[y][x] : this.pixels[y + topMargin][x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#pixelsByTag[tag] = this.pixels.map(row => row.slice());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [tag]
|
||||
* @returns {string[][]}
|
||||
*/
|
||||
getPixels(tag = "default") {
|
||||
return this.#pixelsByTag[tag] ?? this.#pixelsByTag["default"];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {BirdType} [species]
|
||||
* @param {number} direction
|
||||
* @param {number} canvasPixelSize
|
||||
*/
|
||||
draw(ctx, direction, canvasPixelSize, species) {
|
||||
const pixels = this.getPixels(species?.tags[0]);
|
||||
for (let y = 0; y < pixels.length; y++) {
|
||||
const row = pixels[y];
|
||||
for (let x = 0; x < pixels[y].length; x++) {
|
||||
const cell = direction === Directions.LEFT ? row[x] : row[pixels[y].length - x - 1];
|
||||
ctx.fillStyle = species?.colors[cell] ?? cell;
|
||||
ctx.fillRect(x * canvasPixelSize, y * canvasPixelSize, canvasPixelSize, canvasPixelSize);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Frame;
|
||||
12
src/layer.js
Normal file
12
src/layer.js
Normal file
@@ -0,0 +1,12 @@
|
||||
class Layer {
|
||||
/**
|
||||
* @param {string[][]} pixels
|
||||
* @param {string} [tag]
|
||||
*/
|
||||
constructor(pixels, tag = "default") {
|
||||
this.pixels = pixels;
|
||||
this.tag = tag;
|
||||
}
|
||||
}
|
||||
|
||||
export default Layer;
|
||||
139
src/menu.js
Normal file
139
src/menu.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
isDebug,
|
||||
makeElement,
|
||||
onClick,
|
||||
makeDraggable,
|
||||
makeClosable,
|
||||
error
|
||||
} from './shared.js';
|
||||
|
||||
export const MENU_ID = "birb-menu";
|
||||
export const MENU_EXIT_ID = "birb-menu-exit";
|
||||
|
||||
export class MenuItem {
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {() => void} action
|
||||
* @param {boolean} [removeMenu]
|
||||
* @param {boolean} [isDebug]
|
||||
*/
|
||||
constructor(text, action, removeMenu = true, isDebug = false) {
|
||||
this.text = text;
|
||||
this.action = action;
|
||||
this.removeMenu = removeMenu;
|
||||
this.isDebug = isDebug;
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugMenuItem extends MenuItem {
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {() => void} action
|
||||
*/
|
||||
constructor(text, action, removeMenu = true) {
|
||||
super(text, action, removeMenu, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class Separator extends MenuItem {
|
||||
constructor() {
|
||||
super("", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MenuItem} item
|
||||
* @param {() => void} removeMenuCallback
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function makeMenuItem(item, removeMenuCallback) {
|
||||
if (item instanceof Separator) {
|
||||
return makeElement("birb-window-separator");
|
||||
}
|
||||
let menuItem = makeElement("birb-menu-item", item.text);
|
||||
onClick(menuItem, () => {
|
||||
if (item.removeMenu) {
|
||||
removeMenuCallback();
|
||||
}
|
||||
item.action();
|
||||
});
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu to the page if it doesn't already exist
|
||||
* @param {MenuItem[]} menuItems
|
||||
* @param {string} title
|
||||
* @param {(menu: HTMLElement) => void} updateLocationCallback
|
||||
*/
|
||||
export function insertMenu(menuItems, title, updateLocationCallback) {
|
||||
if (document.querySelector("#" + MENU_ID)) {
|
||||
return;
|
||||
}
|
||||
let menu = makeElement("birb-window", undefined, MENU_ID);
|
||||
let header = makeElement("birb-window-header");
|
||||
header.innerHTML = `<div class="birb-window-title">${title}</div>`;
|
||||
let content = makeElement("birb-window-content");
|
||||
const removeCallback = () => removeMenu();
|
||||
for (const item of menuItems) {
|
||||
if (!item.isDebug || isDebug()) {
|
||||
content.appendChild(makeMenuItem(item, removeCallback));
|
||||
}
|
||||
}
|
||||
menu.appendChild(header);
|
||||
menu.appendChild(content);
|
||||
document.body.appendChild(menu);
|
||||
makeDraggable(document.querySelector(".birb-window-header"));
|
||||
|
||||
let menuExit = makeElement("birb-window-exit", undefined, MENU_EXIT_ID);
|
||||
onClick(menuExit, removeCallback);
|
||||
document.body.appendChild(menuExit);
|
||||
makeClosable(removeCallback);
|
||||
|
||||
updateLocationCallback(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the menu from the page
|
||||
*/
|
||||
export function removeMenu() {
|
||||
const menu = document.querySelector("#" + MENU_ID);
|
||||
if (menu) {
|
||||
menu.remove();
|
||||
}
|
||||
const exitMenu = document.querySelector("#" + MENU_EXIT_ID);
|
||||
if (exitMenu) {
|
||||
exitMenu.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether the menu element is on the page
|
||||
*/
|
||||
export function isMenuOpen() {
|
||||
return document.querySelector("#" + MENU_ID) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MenuItem[]} menuItems
|
||||
* @param {(menu: HTMLElement) => void} updateLocationCallback
|
||||
*/
|
||||
export function switchMenuItems(menuItems, updateLocationCallback) {
|
||||
const menu = document.querySelector("#" + MENU_ID);
|
||||
if (!menu || !(menu instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const content = menu.querySelector(".birb-window-content");
|
||||
if (!content) {
|
||||
error("Birb: Content not found");
|
||||
return;
|
||||
}
|
||||
content.innerHTML = "";
|
||||
const removeCallback = () => removeMenu();
|
||||
for (const item of menuItems) {
|
||||
if (!item.isDebug || isDebug()) {
|
||||
content.appendChild(makeMenuItem(item, removeCallback));
|
||||
}
|
||||
}
|
||||
updateLocationCallback(menu);
|
||||
}
|
||||
185
src/shared.js
Normal file
185
src/shared.js
Normal file
@@ -0,0 +1,185 @@
|
||||
export const Directions = {
|
||||
LEFT: -1,
|
||||
RIGHT: 1,
|
||||
};
|
||||
|
||||
let debugMode = location.hostname === "127.0.0.1";
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether debug mode is enabled
|
||||
*/
|
||||
export function isDebug() {
|
||||
return debugMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
export function setDebug(value) {
|
||||
debugMode = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML element with the specified parameters
|
||||
* @param {string} className
|
||||
* @param {string} [textContent]
|
||||
* @param {string} [id]
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
export function makeElement(className, textContent, id) {
|
||||
const element = document.createElement("div");
|
||||
element.classList.add(className);
|
||||
if (textContent) {
|
||||
element.textContent = textContent;
|
||||
}
|
||||
if (id) {
|
||||
element.id = id;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Document|Element} element
|
||||
* @param {(e: Event) => void} action
|
||||
*/
|
||||
export function onClick(element, action) {
|
||||
element.addEventListener("click", (e) => action(e));
|
||||
element.addEventListener("touchend", (e) => {
|
||||
if (e instanceof TouchEvent === false) {
|
||||
return;
|
||||
} else if (element instanceof HTMLElement === false) {
|
||||
return;
|
||||
}
|
||||
const touch = e.changedTouches[0];
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (
|
||||
touch.clientX >= rect.left &&
|
||||
touch.clientX <= rect.right &&
|
||||
touch.clientY >= rect.top &&
|
||||
touch.clientY <= rect.bottom
|
||||
) {
|
||||
action(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement|null} element The element to detect drag events on
|
||||
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
||||
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
||||
*/
|
||||
export function makeDraggable(element, parent = true, callback = () => { }) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isMouseDown = false;
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
let elementToMove = parent ? element.parentElement : element;
|
||||
|
||||
if (!elementToMove) {
|
||||
error("Birb: Parent element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
element.addEventListener("mousedown", (e) => {
|
||||
isMouseDown = true;
|
||||
offsetX = e.clientX - elementToMove.offsetLeft;
|
||||
offsetY = e.clientY - elementToMove.offsetTop;
|
||||
});
|
||||
|
||||
element.addEventListener("touchstart", (e) => {
|
||||
isMouseDown = true;
|
||||
const touch = e.touches[0];
|
||||
offsetX = touch.clientX - elementToMove.offsetLeft;
|
||||
offsetY = touch.clientY - elementToMove.offsetTop;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", (e) => {
|
||||
if (isMouseDown) {
|
||||
callback(elementToMove.offsetTop, elementToMove.offsetLeft);
|
||||
e.preventDefault();
|
||||
}
|
||||
isMouseDown = false;
|
||||
});
|
||||
|
||||
document.addEventListener("touchend", (e) => {
|
||||
if (isMouseDown) {
|
||||
callback(elementToMove.offsetTop, elementToMove.offsetLeft);
|
||||
e.preventDefault();
|
||||
}
|
||||
isMouseDown = false;
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
if (isMouseDown) {
|
||||
elementToMove.style.left = `${Math.max(0, e.clientX - offsetX)}px`;
|
||||
elementToMove.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("touchmove", (e) => {
|
||||
if (isMouseDown) {
|
||||
const touch = e.touches[0];
|
||||
elementToMove.style.left = `${Math.max(0, touch.clientX - offsetX)}px`;
|
||||
elementToMove.style.top = `${Math.max(0, touch.clientY - offsetY)}px`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => void} func
|
||||
* @param {Element} [closeButton]
|
||||
*/
|
||||
export function makeClosable(func, closeButton) {
|
||||
if (closeButton) {
|
||||
onClick(closeButton, func);
|
||||
}
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (closeButton && !document.body.contains(closeButton)) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
func();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Whether the user is on a mobile device
|
||||
*/
|
||||
export function isMobile() {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function log() {
|
||||
console.log("Birb: ", ...arguments);
|
||||
}
|
||||
|
||||
export function debug() {
|
||||
if (isDebug()) {
|
||||
console.debug("Birb: ", ...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
export function error() {
|
||||
console.error("Birb: ", ...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a layer from a sprite sheet array
|
||||
* @param {string[][]} spriteSheet The sprite sheet pixel array
|
||||
* @param {number} spriteIndex The sprite index
|
||||
* @param {number} width The width of each sprite
|
||||
* @returns {string[][]}
|
||||
*/
|
||||
export function getLayer(spriteSheet, spriteIndex, width) {
|
||||
// From an array of a horizontal sprite sheet, get the layer for a specific sprite
|
||||
const layer = [];
|
||||
for (let y = 0; y < width; y++) {
|
||||
layer.push(spriteSheet[y].slice(spriteIndex * width, (spriteIndex + 1) * width));
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
199
src/sprites.js
Normal file
199
src/sprites.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/** Indicators for parts of the base bird sprite sheet */
|
||||
export const SPRITE = {
|
||||
THEME_HIGHLIGHT: "theme-highlight",
|
||||
TRANSPARENT: "transparent",
|
||||
OUTLINE: "outline",
|
||||
BORDER: "border",
|
||||
FOOT: "foot",
|
||||
BEAK: "beak",
|
||||
EYE: "eye",
|
||||
FACE: "face",
|
||||
HOOD: "hood",
|
||||
NOSE: "nose",
|
||||
BELLY: "belly",
|
||||
UNDERBELLY: "underbelly",
|
||||
WING: "wing",
|
||||
WING_EDGE: "wing-edge",
|
||||
HEART: "heart",
|
||||
HEART_BORDER: "heart-border",
|
||||
HEART_SHINE: "heart-shine",
|
||||
FEATHER_SPINE: "feather-spine",
|
||||
};
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
export const SPRITE_SHEET_COLOR_MAP = {
|
||||
"transparent": SPRITE.TRANSPARENT,
|
||||
"#ffffff": SPRITE.BORDER,
|
||||
"#000000": SPRITE.OUTLINE,
|
||||
"#010a19": SPRITE.BEAK,
|
||||
"#190301": SPRITE.EYE,
|
||||
"#af8e75": SPRITE.FOOT,
|
||||
"#639bff": SPRITE.FACE,
|
||||
"#99e550": SPRITE.HOOD,
|
||||
"#d95763": SPRITE.NOSE,
|
||||
"#f8b143": SPRITE.BELLY,
|
||||
"#ec8637": SPRITE.UNDERBELLY,
|
||||
"#578ae6": SPRITE.WING,
|
||||
"#326ed9": SPRITE.WING_EDGE,
|
||||
"#c82e2e": SPRITE.HEART,
|
||||
"#501a1a": SPRITE.HEART_BORDER,
|
||||
"#ff6b6b": SPRITE.HEART_SHINE,
|
||||
"#373737": SPRITE.FEATHER_SPINE,
|
||||
};
|
||||
|
||||
export class BirdType {
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} description
|
||||
* @param {Record<string, string>} colors
|
||||
* @param {string[]} [tags]
|
||||
*/
|
||||
constructor(name, description, colors, tags = []) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
const defaultColors = {
|
||||
[SPRITE.TRANSPARENT]: "transparent",
|
||||
[SPRITE.OUTLINE]: "#000000",
|
||||
[SPRITE.BORDER]: "#ffffff",
|
||||
[SPRITE.BEAK]: "#000000",
|
||||
[SPRITE.EYE]: "#000000",
|
||||
[SPRITE.HEART]: "#c82e2e",
|
||||
[SPRITE.HEART_BORDER]: "#501a1a",
|
||||
[SPRITE.HEART_SHINE]: "#ff6b6b",
|
||||
[SPRITE.FEATHER_SPINE]: "#373737",
|
||||
[SPRITE.HOOD]: colors.face,
|
||||
[SPRITE.NOSE]: colors.face,
|
||||
};
|
||||
/** @type {Record<string, string>} */
|
||||
this.colors = { ...defaultColors, ...colors, [SPRITE.THEME_HIGHLIGHT]: colors[SPRITE.THEME_HIGHLIGHT] ?? colors.hood ?? colors.face };
|
||||
this.tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Record<string, BirdType>} */
|
||||
export const SPECIES = {
|
||||
bluebird: new BirdType("Eastern Bluebird",
|
||||
"Native to North American and very social, though can be timid around people.", {
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#639bff",
|
||||
[SPRITE.BELLY]: "#f8b143",
|
||||
[SPRITE.UNDERBELLY]: "#ec8637",
|
||||
[SPRITE.WING]: "#578ae6",
|
||||
[SPRITE.WING_EDGE]: "#326ed9",
|
||||
}),
|
||||
shimaEnaga: new BirdType("Shima Enaga",
|
||||
"Small, fluffy birds found in the snowy regions of Japan, these birds are highly sought after by ornithologists and nature photographers.", {
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#ffffff",
|
||||
[SPRITE.BELLY]: "#ebe9e8",
|
||||
[SPRITE.UNDERBELLY]: "#ebd9d0",
|
||||
[SPRITE.WING]: "#f3d3c1",
|
||||
[SPRITE.WING_EDGE]: "#2d2d2dff",
|
||||
[SPRITE.THEME_HIGHLIGHT]: "#d7ac93",
|
||||
}),
|
||||
tuftedTitmouse: new BirdType("Tufted Titmouse",
|
||||
"Native to the eastern United States, full of personality, and notably my wife's favorite bird.", {
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#c7cad7",
|
||||
[SPRITE.BELLY]: "#e4e5eb",
|
||||
[SPRITE.UNDERBELLY]: "#d7cfcb",
|
||||
[SPRITE.WING]: "#b1b5c5",
|
||||
[SPRITE.WING_EDGE]: "#9d9fa9",
|
||||
}, ["tuft"]),
|
||||
europeanRobin: new BirdType("European Robin",
|
||||
"Native to western Europe, this is the quintessential robin. Quite friendly, you'll often find them searching for worms.", {
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#ffaf34",
|
||||
[SPRITE.HOOD]: "#aaa094",
|
||||
[SPRITE.BELLY]: "#ffaf34",
|
||||
[SPRITE.UNDERBELLY]: "#babec2",
|
||||
[SPRITE.WING]: "#aaa094",
|
||||
[SPRITE.WING_EDGE]: "#888580",
|
||||
[SPRITE.THEME_HIGHLIGHT]: "#ffaf34",
|
||||
}),
|
||||
redCardinal: new BirdType("Red Cardinal",
|
||||
"Native to the eastern United States, this strikingly red bird is hard to miss.", {
|
||||
[SPRITE.BEAK]: "#d93619",
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#31353d",
|
||||
[SPRITE.HOOD]: "#e83a1b",
|
||||
[SPRITE.BELLY]: "#e83a1b",
|
||||
[SPRITE.UNDERBELLY]: "#dc3719",
|
||||
[SPRITE.WING]: "#d23215",
|
||||
[SPRITE.WING_EDGE]: "#b1321c",
|
||||
}, ["tuft"]),
|
||||
americanGoldfinch: new BirdType("American Goldfinch",
|
||||
"Coloured a brilliant yellow, this bird feeds almost entirely on the seeds of plants such as thistle, sunflowers, and coneflowers.", {
|
||||
[SPRITE.BEAK]: "#ffaf34",
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#fff255",
|
||||
[SPRITE.NOSE]: "#383838",
|
||||
[SPRITE.HOOD]: "#383838",
|
||||
[SPRITE.BELLY]: "#fff255",
|
||||
[SPRITE.UNDERBELLY]: "#f5ea63",
|
||||
[SPRITE.WING]: "#e8e079",
|
||||
[SPRITE.WING_EDGE]: "#191919",
|
||||
[SPRITE.THEME_HIGHLIGHT]: "#ffcc00"
|
||||
}),
|
||||
barnSwallow: new BirdType("Barn Swallow",
|
||||
"Agile birds that often roost in man-made structures, these birds are known to build nests near Ospreys for protection.", {
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#db7c4d",
|
||||
[SPRITE.BELLY]: "#f7e1c9",
|
||||
[SPRITE.UNDERBELLY]: "#ebc9a3",
|
||||
[SPRITE.WING]: "#2252a9",
|
||||
[SPRITE.WING_EDGE]: "#1c448b",
|
||||
[SPRITE.HOOD]: "#2252a9",
|
||||
}),
|
||||
mistletoebird: new BirdType("Mistletoebird",
|
||||
"Native to Australia, these birds eat mainly mistletoe and in turn spread the seeds far and wide.", {
|
||||
[SPRITE.FOOT]: "#6c6a7c",
|
||||
[SPRITE.FACE]: "#352e6d",
|
||||
[SPRITE.BELLY]: "#fd6833",
|
||||
[SPRITE.UNDERBELLY]: "#e6e1d8",
|
||||
[SPRITE.WING]: "#342b7c",
|
||||
[SPRITE.WING_EDGE]: "#282065",
|
||||
}),
|
||||
redAvadavat: new BirdType("Red Avadavat",
|
||||
"Native to India and southeast Asia, these birds are also known as Strawberry Finches due to their speckled plumage.", {
|
||||
[SPRITE.BEAK]: "#f71919",
|
||||
[SPRITE.FOOT]: "#af7575",
|
||||
[SPRITE.FACE]: "#cb092b",
|
||||
[SPRITE.BELLY]: "#ae1724",
|
||||
[SPRITE.UNDERBELLY]: "#831b24",
|
||||
[SPRITE.WING]: "#7e3030",
|
||||
[SPRITE.WING_EDGE]: "#490f0f",
|
||||
}),
|
||||
scarletRobin: new BirdType("Scarlet Robin",
|
||||
"Native to Australia, this striking robin can be found in Eucalyptus forests.", {
|
||||
[SPRITE.FOOT]: "#494949",
|
||||
[SPRITE.FACE]: "#3d3d3d",
|
||||
[SPRITE.BELLY]: "#fc5633",
|
||||
[SPRITE.UNDERBELLY]: "#dcdcdc",
|
||||
[SPRITE.WING]: "#2b2b2b",
|
||||
[SPRITE.WING_EDGE]: "#ebebeb",
|
||||
[SPRITE.THEME_HIGHLIGHT]: "#fc5633",
|
||||
}),
|
||||
americanRobin: new BirdType("American Robin",
|
||||
"While not a true robin, this social North American bird is so named due to its orange coloring. It seems unbothered by nearby humans.", {
|
||||
[SPRITE.BEAK]: "#e89f30",
|
||||
[SPRITE.FOOT]: "#9f8075",
|
||||
[SPRITE.FACE]: "#2d2d2d",
|
||||
[SPRITE.BELLY]: "#eb7a3a",
|
||||
[SPRITE.UNDERBELLY]: "#eb7a3a",
|
||||
[SPRITE.WING]: "#444444",
|
||||
[SPRITE.WING_EDGE]: "#232323",
|
||||
[SPRITE.THEME_HIGHLIGHT]: "#eb7a3a",
|
||||
}),
|
||||
carolinaWren: new BirdType("Carolina Wren",
|
||||
"Native to the eastern United States, these little birds are known for their curious and energetic nature.", {
|
||||
[SPRITE.FOOT]: "#af8e75",
|
||||
[SPRITE.FACE]: "#edc7a9",
|
||||
[SPRITE.NOSE]: "#f7eee5",
|
||||
[SPRITE.HOOD]: "#c58a5b",
|
||||
[SPRITE.BELLY]: "#e1b796",
|
||||
[SPRITE.UNDERBELLY]: "#c79e7c",
|
||||
[SPRITE.WING]: "#c58a5b",
|
||||
[SPRITE.WING_EDGE]: "#866348",
|
||||
}),
|
||||
};
|
||||
170
src/stickyNotes.js
Normal file
170
src/stickyNotes.js
Normal file
@@ -0,0 +1,170 @@
|
||||
import {
|
||||
makeElement,
|
||||
makeDraggable,
|
||||
makeClosable
|
||||
} from './shared.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} SavedStickyNote
|
||||
* @property {string} id
|
||||
* @property {string} site
|
||||
* @property {string} content
|
||||
* @property {number} top
|
||||
* @property {number} left
|
||||
*/
|
||||
|
||||
export class StickyNote {
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {string} [site]
|
||||
* @param {string} [content]
|
||||
* @param {number} [top]
|
||||
* @param {number} [left]
|
||||
*/
|
||||
constructor(id, site = "", content = "", top = 0, left = 0) {
|
||||
this.id = id;
|
||||
this.site = site;
|
||||
this.content = content;
|
||||
this.top = top;
|
||||
this.left = left;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse URL parameters into a key-value map
|
||||
* @param {string} url
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
export function parseUrlParams(url) {
|
||||
const queryString = url.split("?")[1];
|
||||
if (!queryString) return {};
|
||||
|
||||
return queryString.split("&").reduce((params, param) => {
|
||||
const [key, value] = param.split("=");
|
||||
return { ...params, [key]: value };
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StickyNote} stickyNote
|
||||
* @returns {boolean} Whether the given sticky note is applicable to the current site/page
|
||||
*/
|
||||
export function isStickyNoteApplicable(stickyNote) {
|
||||
const stickyNoteUrl = stickyNote.site;
|
||||
const currentUrl = window.location.href;
|
||||
const stickyNoteWebsite = stickyNoteUrl.split("?")[0];
|
||||
const currentWebsite = currentUrl.split("?")[0];
|
||||
|
||||
if (stickyNoteWebsite !== currentWebsite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stickyNoteParams = parseUrlParams(stickyNoteUrl);
|
||||
const currentParams = parseUrlParams(currentUrl);
|
||||
|
||||
if (window.location.hostname === "www.youtube.com") {
|
||||
if (currentParams.v !== undefined && currentParams.v !== stickyNoteParams.v) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StickyNote} stickyNote
|
||||
* @param {() => void} onSave
|
||||
* @param {() => void} onDelete
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
export function renderStickyNote(stickyNote, onSave, onDelete) {
|
||||
let html = `
|
||||
<div class="birb-window-header">
|
||||
<div class="birb-window-title">Sticky Note</div>
|
||||
<div class="birb-window-close">x</div>
|
||||
</div>
|
||||
<div class="birb-window-content">
|
||||
<textarea class="birb-sticky-note-input" style="width: 150px;" placeholder="Write your notes here and they'll stick to the page!">${stickyNote.content}</textarea>
|
||||
</div>`
|
||||
const noteElement = makeElement("birb-window");
|
||||
noteElement.classList.add("birb-sticky-note");
|
||||
noteElement.innerHTML = html;
|
||||
|
||||
noteElement.style.top = `${stickyNote.top}px`;
|
||||
noteElement.style.left = `${stickyNote.left}px`;
|
||||
document.body.appendChild(noteElement);
|
||||
|
||||
makeDraggable(noteElement.querySelector(".birb-window-header"), true, (top, left) => {
|
||||
stickyNote.top = top;
|
||||
stickyNote.left = left;
|
||||
onSave();
|
||||
});
|
||||
|
||||
const closeButton = noteElement.querySelector(".birb-window-close");
|
||||
if (closeButton) {
|
||||
makeClosable(() => {
|
||||
if (confirm("Are you sure you want to delete this sticky note?")) {
|
||||
onDelete();
|
||||
noteElement.remove();
|
||||
}
|
||||
}, closeButton);
|
||||
}
|
||||
|
||||
const textarea = noteElement.querySelector(".birb-sticky-note-input");
|
||||
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
||||
let saveTimeout;
|
||||
// Save after debounce
|
||||
textarea.addEventListener("input", () => {
|
||||
stickyNote.content = textarea.value;
|
||||
if (saveTimeout) {
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
saveTimeout = setTimeout(() => {
|
||||
onSave();
|
||||
}, 250);
|
||||
});
|
||||
}
|
||||
|
||||
// On window resize
|
||||
window.addEventListener("resize", () => {
|
||||
const modTop = `${stickyNote.top - Math.min(window.innerHeight - noteElement.offsetHeight, stickyNote.top)}px`;
|
||||
const modLeft = `${stickyNote.left - Math.min(window.innerWidth - noteElement.offsetWidth, stickyNote.left)}px`;
|
||||
noteElement.style.transform = `scale(var(--birb-ui-scale)) translate(-${modLeft}, -${modTop})`;
|
||||
});
|
||||
|
||||
return noteElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StickyNote[]} stickyNotes
|
||||
* @param {() => void} onSave
|
||||
* @param {(note: StickyNote) => void} onDelete
|
||||
*/
|
||||
export function drawStickyNotes(stickyNotes, onSave, onDelete) {
|
||||
// Remove all existing sticky notes
|
||||
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
||||
existingNotes.forEach(note => note.remove());
|
||||
// Render all sticky notes
|
||||
for (let stickyNote of stickyNotes) {
|
||||
if (isStickyNoteApplicable(stickyNote)) {
|
||||
renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StickyNote[]} stickyNotes
|
||||
* @param {() => void} onSave
|
||||
* @param {(note: StickyNote) => void} onDelete
|
||||
*/
|
||||
export function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
||||
const id = Date.now().toString();
|
||||
const site = window.location.href;
|
||||
const stickyNote = new StickyNote(id, site, "");
|
||||
const element = renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
||||
element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`;
|
||||
element.style.top = `${window.scrollY + window.innerHeight / 2 - element.offsetHeight / 2}px`;
|
||||
stickyNote.top = parseInt(element.style.top, 10);
|
||||
stickyNote.left = parseInt(element.style.left, 10);
|
||||
stickyNotes.push(stickyNote);
|
||||
onSave();
|
||||
}
|
||||
Reference in New Issue
Block a user