mirror of
https://github.com/maunium/stickerpicker.git
synced 2025-08-28 10:51:45 +02:00
Compare commits
36 Commits
server
...
398fc952b6
Author | SHA1 | Date | |
---|---|---|---|
|
398fc952b6 | ||
|
91eca68c7b | ||
|
182cafe13a | ||
|
c4588f19a7 | ||
|
2e1b333cbb | ||
|
64b7b1507f | ||
|
0897ce6c20 | ||
|
380a070e71 | ||
|
59bf7ef252 | ||
|
8c4291d266 | ||
|
cfd0b8e292 | ||
|
f59406a47a | ||
|
99ced8878a | ||
|
046779d102 | ||
|
ef844a0ff8 | ||
|
502d91fc75 | ||
|
591137ccb3 | ||
|
f29c165357 | ||
|
7939793351 | ||
|
e0d895f22a | ||
|
5d3c7d1e2f | ||
|
ec8eeeeaf5 | ||
|
57fde6fcad | ||
|
9443e79e97 | ||
|
85813b22e5 | ||
|
569d9815c6 | ||
|
0f7b678f57 | ||
|
b884a9c387 | ||
|
ba0096275c | ||
|
3916ade97b | ||
|
dab2420cd4 | ||
|
601d2acc32 | ||
|
21d4f5cce6 | ||
|
9350d5f27b | ||
|
add27513fe | ||
|
66d5b90ea1 |
@@ -11,8 +11,14 @@ For setup and usage instructions, please visit the [wiki](https://github.com/mau
|
|||||||
* [Enabling the widget](https://github.com/maunium/stickerpicker/wiki/Enabling-the-widget)
|
* [Enabling the widget](https://github.com/maunium/stickerpicker/wiki/Enabling-the-widget)
|
||||||
* [Hosting on GitHub pages](https://github.com/maunium/stickerpicker/wiki/Hosting-on-GitHub-pages)
|
* [Hosting on GitHub pages](https://github.com/maunium/stickerpicker/wiki/Hosting-on-GitHub-pages)
|
||||||
|
|
||||||
|
If you prefer video tutorials, [Brodie Robertson](https://www.youtube.com/c/BrodieRobertson) has made a great video on setting up the picker and creating some packs: https://youtu.be/Yz3H6KJTEI0.
|
||||||
|
|
||||||
## Comparison with other sticker pickers
|
## Comparison with other sticker pickers
|
||||||
|
|
||||||
|
* Scalar is the default integration manager in Element, which can't be self-hosted and only supports predefined sticker packs.
|
||||||
|
* [Dimension](https://github.com/turt2live/matrix-dimension) is an alternate integration manager. It can be self-hosted, but it's more difficult than Maunium sticker picker.
|
||||||
|
* Maunium sticker picker is just a sticker picker rather than a full integration manager. It's much simpler than integration managers, but currently has to be set up manually per-user.
|
||||||
|
|
||||||
| Feature | Scalar | Dimension | Maunium sticker picker |
|
| Feature | Scalar | Dimension | Maunium sticker picker |
|
||||||
|---------------------------------|--------|-----------|------------------------|
|
|---------------------------------|--------|-----------|------------------------|
|
||||||
| Free software | ❌ | ✔️ | ✔️ |
|
| Free software | ❌ | ✔️ | ✔️ |
|
||||||
|
3
setup.py
3
setup.py
@@ -45,9 +45,10 @@ setuptools.setup(
|
|||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
],
|
],
|
||||||
entry_points={"console_scripts": [
|
entry_points={"console_scripts": [
|
||||||
"sticker-import=sticker.import:cmd",
|
"sticker-import=sticker.stickerimport:cmd",
|
||||||
"sticker-pack=sticker.pack:cmd",
|
"sticker-pack=sticker.pack:cmd",
|
||||||
]},
|
]},
|
||||||
)
|
)
|
||||||
|
@@ -42,6 +42,7 @@ if TYPE_CHECKING:
|
|||||||
url: str
|
url: str
|
||||||
info: MediaInfo
|
info: MediaInfo
|
||||||
id: str
|
id: str
|
||||||
|
msgtype: str
|
||||||
else:
|
else:
|
||||||
MediaInfo = None
|
MediaInfo = None
|
||||||
StickerInfo = None
|
StickerInfo = None
|
||||||
@@ -54,17 +55,22 @@ async def load_config(path: str) -> None:
|
|||||||
config = json.load(config_file)
|
config = json.load(config_file)
|
||||||
homeserver_url = config["homeserver"]
|
homeserver_url = config["homeserver"]
|
||||||
access_token = config["access_token"]
|
access_token = config["access_token"]
|
||||||
|
giphy_api_key = config["giphy_api_key"]
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print("Matrix config file not found. Please enter your homeserver and access token.")
|
print("Matrix config file not found. Please enter your homeserver and access token. Enter the Giphy API token if required, leave blank to disable the gif picker.")
|
||||||
homeserver_url = input("Homeserver URL: ")
|
homeserver_url = input("Homeserver URL: ")
|
||||||
access_token = input("Access token: ")
|
access_token = input("Access token: ")
|
||||||
|
giphy_api_key = input("Giphy API key: ")
|
||||||
whoami_url = URL(homeserver_url) / "_matrix" / "client" / "r0" / "account" / "whoami"
|
whoami_url = URL(homeserver_url) / "_matrix" / "client" / "r0" / "account" / "whoami"
|
||||||
|
if whoami_url.scheme not in ("https", "http"):
|
||||||
|
whoami_url = whoami_url.with_scheme("https")
|
||||||
user_id = await whoami(whoami_url, access_token)
|
user_id = await whoami(whoami_url, access_token)
|
||||||
with open(path, "w") as config_file:
|
with open(path, "w") as config_file:
|
||||||
json.dump({
|
json.dump({
|
||||||
"homeserver": homeserver_url,
|
"homeserver": homeserver_url,
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"access_token": access_token
|
"access_token": access_token,
|
||||||
|
"giphy_api_key": giphy_api_key
|
||||||
}, config_file)
|
}, config_file)
|
||||||
print(f"Wrote config to {path}")
|
print(f"Wrote config to {path}")
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from functools import partial
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import os.path
|
import os.path
|
||||||
import json
|
import json
|
||||||
@@ -21,6 +22,7 @@ from PIL import Image
|
|||||||
|
|
||||||
from . import matrix
|
from . import matrix
|
||||||
|
|
||||||
|
open_utf8 = partial(open, encoding='UTF-8')
|
||||||
|
|
||||||
def convert_image(data: bytes) -> (bytes, int, int):
|
def convert_image(data: bytes) -> (bytes, int, int):
|
||||||
image: Image.Image = Image.open(BytesIO(data)).convert("RGBA")
|
image: Image.Image = Image.open(BytesIO(data)).convert("RGBA")
|
||||||
@@ -41,7 +43,7 @@ def convert_image(data: bytes) -> (bytes, int, int):
|
|||||||
def add_to_index(name: str, output_dir: str) -> None:
|
def add_to_index(name: str, output_dir: str) -> None:
|
||||||
index_path = os.path.join(output_dir, "index.json")
|
index_path = os.path.join(output_dir, "index.json")
|
||||||
try:
|
try:
|
||||||
with open(index_path) as index_file:
|
with open_utf8(index_path) as index_file:
|
||||||
index_data = json.load(index_file)
|
index_data = json.load(index_file)
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
index_data = {"packs": []}
|
index_data = {"packs": []}
|
||||||
@@ -49,7 +51,7 @@ def add_to_index(name: str, output_dir: str) -> None:
|
|||||||
index_data["homeserver_url"] = matrix.homeserver_url
|
index_data["homeserver_url"] = matrix.homeserver_url
|
||||||
if name not in index_data["packs"]:
|
if name not in index_data["packs"]:
|
||||||
index_data["packs"].append(name)
|
index_data["packs"].append(name)
|
||||||
with open(index_path, "w") as index_file:
|
with open_utf8(index_path, "w") as index_file:
|
||||||
json.dump(index_data, index_file, indent=" ")
|
json.dump(index_data, index_file, indent=" ")
|
||||||
print(f"Added {name} to {index_path}")
|
print(f"Added {name} to {index_path}")
|
||||||
|
|
||||||
@@ -74,4 +76,5 @@ def make_sticker(mxc: str, width: int, height: int, size: int,
|
|||||||
"mimetype": "image/png",
|
"mimetype": "image/png",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"msgtype": "m.sticker",
|
||||||
}
|
}
|
||||||
|
@@ -93,7 +93,7 @@ async def main(args: argparse.Namespace) -> None:
|
|||||||
dirname = os.path.basename(os.path.abspath(args.path))
|
dirname = os.path.basename(os.path.abspath(args.path))
|
||||||
meta_path = os.path.join(args.path, "pack.json")
|
meta_path = os.path.join(args.path, "pack.json")
|
||||||
try:
|
try:
|
||||||
with open(meta_path) as pack_file:
|
with util.open_utf8(meta_path) as pack_file:
|
||||||
pack = json.load(pack_file)
|
pack = json.load(pack_file)
|
||||||
print(f"Loaded existing pack meta from {meta_path}")
|
print(f"Loaded existing pack meta from {meta_path}")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -112,14 +112,14 @@ async def main(args: argparse.Namespace) -> None:
|
|||||||
if sticker:
|
if sticker:
|
||||||
pack["stickers"].append(sticker)
|
pack["stickers"].append(sticker)
|
||||||
|
|
||||||
with open(meta_path, "w") as pack_file:
|
with util.open_utf8(meta_path, "w") as pack_file:
|
||||||
json.dump(pack, pack_file)
|
json.dump(pack, pack_file)
|
||||||
print(f"Wrote pack to {meta_path}")
|
print(f"Wrote pack to {meta_path}")
|
||||||
|
|
||||||
if args.add_to_index:
|
if args.add_to_index:
|
||||||
picker_file_name = f"{pack['id']}.json"
|
picker_file_name = f"{pack['id']}.json"
|
||||||
picker_pack_path = os.path.join(args.add_to_index, picker_file_name)
|
picker_pack_path = os.path.join(args.add_to_index, picker_file_name)
|
||||||
with open(picker_pack_path, "w") as pack_file:
|
with util.open_utf8(picker_pack_path, "w") as pack_file:
|
||||||
json.dump(pack, pack_file)
|
json.dump(pack, pack_file)
|
||||||
print(f"Copied pack to {picker_pack_path}")
|
print(f"Copied pack to {picker_pack_path}")
|
||||||
util.add_to_index(picker_file_name, args.add_to_index)
|
util.add_to_index(picker_file_name, args.add_to_index)
|
||||||
|
@@ -19,12 +19,12 @@ import json
|
|||||||
index_path = "../web/packs/index.json"
|
index_path = "../web/packs/index.json"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(index_path) as index_file:
|
with util.open_utf8(index_path) as index_file:
|
||||||
index_data = json.load(index_file)
|
index_data = json.load(index_file)
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
index_data = {"packs": []}
|
index_data = {"packs": []}
|
||||||
|
|
||||||
with open(sys.argv[-1]) as file:
|
with util.open_utf8(sys.argv[-1]) as file:
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
|
|
||||||
for pack in data["assets"]:
|
for pack in data["assets"]:
|
||||||
@@ -45,12 +45,12 @@ for pack in data["assets"]:
|
|||||||
}
|
}
|
||||||
filename = f"scalar-{pack['name'].replace(' ', '_')}.json"
|
filename = f"scalar-{pack['name'].replace(' ', '_')}.json"
|
||||||
pack_path = f"web/packs/{filename}"
|
pack_path = f"web/packs/{filename}"
|
||||||
with open(pack_path, "w") as pack_file:
|
with util.open_utf8(pack_path, "w") as pack_file:
|
||||||
json.dump(pack_data, pack_file)
|
json.dump(pack_data, pack_file)
|
||||||
print(f"Wrote {title} to {pack_path}")
|
print(f"Wrote {title} to {pack_path}")
|
||||||
if filename not in index_data["packs"]:
|
if filename not in index_data["packs"]:
|
||||||
index_data["packs"].append(filename)
|
index_data["packs"].append(filename)
|
||||||
|
|
||||||
with open(index_path, "w") as index_file:
|
with util.open_utf8(index_path, "w") as index_file:
|
||||||
json.dump(index_data, index_file, indent=" ")
|
json.dump(index_data, index_file, indent=" ")
|
||||||
print(f"Updated {index_path}")
|
print(f"Updated {index_path}")
|
||||||
|
@@ -71,7 +71,7 @@ async def reupload_pack(client: TelegramClient, pack: StickerSetFull, output_dir
|
|||||||
|
|
||||||
already_uploaded = {}
|
already_uploaded = {}
|
||||||
try:
|
try:
|
||||||
with open(pack_path) as pack_file:
|
with util.open_utf8(pack_path) as pack_file:
|
||||||
existing_pack = json.load(pack_file)
|
existing_pack = json.load(pack_file)
|
||||||
already_uploaded = {int(sticker["net.maunium.telegram.sticker"]["id"]): sticker
|
already_uploaded = {int(sticker["net.maunium.telegram.sticker"]["id"]): sticker
|
||||||
for sticker in existing_pack["stickers"]}
|
for sticker in existing_pack["stickers"]}
|
||||||
@@ -99,7 +99,7 @@ async def reupload_pack(client: TelegramClient, pack: StickerSetFull, output_dir
|
|||||||
doc["body"] = sticker.emoticon
|
doc["body"] = sticker.emoticon
|
||||||
doc["net.maunium.telegram.sticker"]["emoticons"].append(sticker.emoticon)
|
doc["net.maunium.telegram.sticker"]["emoticons"].append(sticker.emoticon)
|
||||||
|
|
||||||
with open(pack_path, "w") as pack_file:
|
with util.open_utf8(pack_path, "w") as pack_file:
|
||||||
json.dump({
|
json.dump({
|
||||||
"title": pack.set.title,
|
"title": pack.set.title,
|
||||||
"id": f"tg-{pack.set.id}",
|
"id": f"tg-{pack.set.id}",
|
||||||
@@ -138,11 +138,12 @@ async def main(args: argparse.Namespace) -> None:
|
|||||||
if args.list:
|
if args.list:
|
||||||
stickers: AllStickers = await client(GetAllStickersRequest(hash=0))
|
stickers: AllStickers = await client(GetAllStickersRequest(hash=0))
|
||||||
index = 1
|
index = 1
|
||||||
width = len(str(stickers.sets))
|
width = len(str(len(stickers.sets)))
|
||||||
print("Your saved sticker packs:")
|
print("Your saved sticker packs:")
|
||||||
for saved_pack in stickers.sets:
|
for saved_pack in stickers.sets:
|
||||||
print(f"{index:>{width}}. {saved_pack.title} "
|
print(f"{index:>{width}}. {saved_pack.title} "
|
||||||
f"(t.me/addstickers/{saved_pack.short_name})")
|
f"(t.me/addstickers/{saved_pack.short_name})")
|
||||||
|
index += 1
|
||||||
elif args.pack[0]:
|
elif args.pack[0]:
|
||||||
input_packs = []
|
input_packs = []
|
||||||
for pack_url in args.pack[0]:
|
for pack_url in args.pack[0]:
|
||||||
@@ -152,7 +153,7 @@ async def main(args: argparse.Namespace) -> None:
|
|||||||
return
|
return
|
||||||
input_packs.append(InputStickerSetShortName(short_name=match.group(1)))
|
input_packs.append(InputStickerSetShortName(short_name=match.group(1)))
|
||||||
for input_pack in input_packs:
|
for input_pack in input_packs:
|
||||||
pack: StickerSetFull = await client(GetStickerSetRequest(input_pack))
|
pack: StickerSetFull = await client(GetStickerSetRequest(input_pack, hash=0))
|
||||||
await reupload_pack(client, pack, args.output_dir)
|
await reupload_pack(client, pack, args.output_dir)
|
||||||
else:
|
else:
|
||||||
parser.print_help()
|
parser.print_help()
|
@@ -1 +1,6 @@
|
|||||||
from .get_version import git_tag, git_revision, version, linkified_version
|
# Generated in setup.py
|
||||||
|
|
||||||
|
git_tag = None
|
||||||
|
git_revision = 'f59406a4'
|
||||||
|
version = '0.1.0+dev.f59406a4'
|
||||||
|
linkified_version = '0.1.0+dev.[f59406a4](https://github.com/maunium/stickerpicker/commit/f59406a47a6778cd402e656ffb64f667335f665a)'
|
||||||
|
23
web/esinstall.js
Normal file
23
web/esinstall.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const { install, printStats } = require("esinstall")
|
||||||
|
|
||||||
|
install(
|
||||||
|
[{
|
||||||
|
specifier: "htm/preact",
|
||||||
|
all: false,
|
||||||
|
default: false,
|
||||||
|
namespace: false,
|
||||||
|
named: ["html", "render", "Component"],
|
||||||
|
}],
|
||||||
|
{
|
||||||
|
dest: "./lib",
|
||||||
|
sourceMap: false,
|
||||||
|
treeshake: true,
|
||||||
|
verbose: true,
|
||||||
|
}
|
||||||
|
).then(data => {
|
||||||
|
const oldPrefix = "web_modules/"
|
||||||
|
const newPrefix = "lib/"
|
||||||
|
const spaces = " ".repeat(oldPrefix.length - newPrefix.length)
|
||||||
|
console.log("Installation complete")
|
||||||
|
console.log(printStats(data.stats).replace(oldPrefix, newPrefix + spaces))
|
||||||
|
})
|
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
|
||||||
<title>Maunium sticker picker</title>
|
<title>Maunium sticker picker</title>
|
||||||
|
<script src="https://unpkg.com/matrix-widget-api@1.6.0/dist/api.js"></script>
|
||||||
<link rel="modulepreload" href="src/widget-api.js"/>
|
<script type="module" src="src/widget-api.js"></script>
|
||||||
<link rel="modulepreload" href="src/frequently-used.js"/>
|
<link rel="modulepreload" href="src/frequently-used.js"/>
|
||||||
<link rel="modulepreload" href="src/spinner.js"/>
|
<link rel="modulepreload" href="src/spinner.js"/>
|
||||||
<link rel="modulepreload" href="lib/htm/preact.js"/>
|
<link rel="modulepreload" href="lib/htm/preact.js"/>
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -4,28 +4,16 @@
|
|||||||
"description": "A fast and simple Matrix sticker picker widget",
|
"description": "A fast and simple Matrix sticker picker widget",
|
||||||
"repository": "https://github.com/maunium/stickerpicker",
|
"repository": "https://github.com/maunium/stickerpicker",
|
||||||
"author": "Tulir Asokan <tulir@maunium.net>",
|
"author": "Tulir Asokan <tulir@maunium.net>",
|
||||||
"license": "MPL-2.0",
|
"license": "AGPL-3.0-or-later",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"snowpack": "snowpack",
|
"esinstall": "node ./esinstall.js",
|
||||||
"sass": "node-sass -o style style/*.sass --output-style compressed"
|
"sass": "sass --no-source-map --style=compressed style/"
|
||||||
},
|
|
||||||
"snowpack": {
|
|
||||||
"install": [
|
|
||||||
"htm/preact"
|
|
||||||
],
|
|
||||||
"installOptions": {
|
|
||||||
"sourceMap": false,
|
|
||||||
"dest": "lib",
|
|
||||||
"treeshake": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"htm": "^3.0.4",
|
"htm": "^3.1.0",
|
||||||
"preact": "^10.4.8",
|
"preact": "^10.5.14",
|
||||||
"snowpack": "^2.10.3"
|
"esinstall": "^1.1.7",
|
||||||
},
|
"sass": "^1.42.1"
|
||||||
"devDependencies": {
|
|
||||||
"node-sass": "^4.14.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
web/packs/index.json
Normal file
16
web/packs/index.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"packs": [
|
||||||
|
"scalar-isabella.json",
|
||||||
|
"scalar-privacy_pam.json",
|
||||||
|
"scalar-sheltie.json",
|
||||||
|
"scalar-stakey.json",
|
||||||
|
"scalar-videoplasty.json",
|
||||||
|
"scalar-geeko.json",
|
||||||
|
"scalar-loading_artist.json",
|
||||||
|
"scalar-rabbit.json",
|
||||||
|
"scalar-smilies.json",
|
||||||
|
"scalar-stickman.json"
|
||||||
|
],
|
||||||
|
"homeserver_url": "https://matrix.intothematrix.in",
|
||||||
|
"giphy_api_key": "Gc7131jiJuvI7IdN0HZ1D7nh0ow5BU6g"
|
||||||
|
}
|
1
web/packs/scalar-geeko.json
Normal file
1
web/packs/scalar-geeko.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"title": "Geeko", "id": "scalar-191580", "stickers": [{"body": "Geeko with a suitcase, wearing a suit", "info": {"h": 256, "mimetype": "image/png", "size": 41241, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 41241, "w": 256}, "thumbnail_url": "mxc://matrix.org/GWyQoBKgXAoIXhBcCMCxmkxd", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/GWyQoBKgXAoIXhBcCMCxmkxd", "id": "GWyQoBKgXAoIXhBcCMCxmkxd"}, {"body": "Geeko driving away in a car waving", "info": {"h": 256, "mimetype": "image/png", "size": 51387, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 51387, "w": 256}, "thumbnail_url": "mxc://matrix.org/JkAPbbuIuOMMbWqhKTNnGETu", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/JkAPbbuIuOMMbWqhKTNnGETu", "id": "JkAPbbuIuOMMbWqhKTNnGETu"}, {"body": "Geeko enjoying his time in an armchair with a cup of tea", "info": {"h": 256, "mimetype": "image/png", "size": 44188, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 44188, "w": 256}, "thumbnail_url": "mxc://matrix.org/AdkCycKHKGfgpyOCrlcSihDK", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/AdkCycKHKGfgpyOCrlcSihDK", "id": "AdkCycKHKGfgpyOCrlcSihDK"}, {"body": "Sick Geeko wrapped in a blanket with a tray of refreshing bevarage", "info": {"h": 256, "mimetype": "image/png", "size": 36364, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 36364, "w": 256}, "thumbnail_url": "mxc://matrix.org/SNkQWaqBkKwIEHQyJmAuhnIL", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/SNkQWaqBkKwIEHQyJmAuhnIL", "id": "SNkQWaqBkKwIEHQyJmAuhnIL"}, {"body": "Geeko in a balerina skirt inviting to dance with them", "info": {"h": 256, "mimetype": "image/png", "size": 34716, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 34716, "w": 256}, "thumbnail_url": "mxc://matrix.org/WLjGVDOlqAPtMaggqcgmjZsB", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/WLjGVDOlqAPtMaggqcgmjZsB", "id": "WLjGVDOlqAPtMaggqcgmjZsB"}, {"body": "Geeko laying on a cloud", "info": {"h": 256, "mimetype": "image/png", "size": 39189, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 39189, "w": 256}, "thumbnail_url": "mxc://matrix.org/ouaitVRUeqIJCuMuKLNtzOPl", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/ouaitVRUeqIJCuMuKLNtzOPl", "id": "ouaitVRUeqIJCuMuKLNtzOPl"}, {"body": "Geeko stretching", "info": {"h": 256, "mimetype": "image/png", "size": 34670, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 34670, "w": 256}, "thumbnail_url": "mxc://matrix.org/DXEyavdgypGEPLeWdmaAkOAG", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/DXEyavdgypGEPLeWdmaAkOAG", "id": "DXEyavdgypGEPLeWdmaAkOAG"}, {"body": "Geeko aproaching Nirvana", "info": {"h": 256, "mimetype": "image/png", "size": 42035, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 42035, "w": 256}, "thumbnail_url": "mxc://matrix.org/DRCkchiNQgASUCFTxFDDfhvi", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/DRCkchiNQgASUCFTxFDDfhvi", "id": "DRCkchiNQgASUCFTxFDDfhvi"}, {"body": "Geeko dressed in a paper airplane, running", "info": {"h": 256, "mimetype": "image/png", "size": 49568, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 49568, "w": 256}, "thumbnail_url": "mxc://matrix.org/YrJQDfZpESKIYdUzvhMDXBLU", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/YrJQDfZpESKIYdUzvhMDXBLU", "id": "YrJQDfZpESKIYdUzvhMDXBLU"}, {"body": "Geeko drawing an animal", "info": {"h": 256, "mimetype": "image/png", "size": 54112, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 54112, "w": 256}, "thumbnail_url": "mxc://matrix.org/bHmdDCDjmolEjrxAFDGuHoJa", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/bHmdDCDjmolEjrxAFDGuHoJa", "id": "bHmdDCDjmolEjrxAFDGuHoJa"}]}
|
1
web/packs/scalar-isabella.json
Normal file
1
web/packs/scalar-isabella.json
Normal file
File diff suppressed because one or more lines are too long
1
web/packs/scalar-loading_artist.json
Normal file
1
web/packs/scalar-loading_artist.json
Normal file
File diff suppressed because one or more lines are too long
1
web/packs/scalar-privacy_pam.json
Normal file
1
web/packs/scalar-privacy_pam.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"title": "Privacy Pam", "id": "scalar-191583", "stickers": [{"body": "Privacy Pam is Angry", "info": {"h": 256, "mimetype": "image/png", "size": 184861, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 184861, "w": 256}, "thumbnail_url": "mxc://matrix.org/WYZLGkpAXwgOftafRtSQVNYF", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/WYZLGkpAXwgOftafRtSQVNYF", "id": "WYZLGkpAXwgOftafRtSQVNYF"}, {"body": "Privacy Pam Cries", "info": {"h": 256, "mimetype": "image/png", "size": 164740, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 164740, "w": 256}, "thumbnail_url": "mxc://matrix.org/aVOIYKvTRBiKqZbxomKeuwYD", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/aVOIYKvTRBiKqZbxomKeuwYD", "id": "aVOIYKvTRBiKqZbxomKeuwYD"}, {"body": "Privacy Pam is Happy", "info": {"h": 256, "mimetype": "image/png", "size": 172907, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 172907, "w": 256}, "thumbnail_url": "mxc://matrix.org/FZolsrwTDUJoLlGfWHffwuFP", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/FZolsrwTDUJoLlGfWHffwuFP", "id": "FZolsrwTDUJoLlGfWHffwuFP"}, {"body": "Privacy Pam Laughs", "info": {"h": 256, "mimetype": "image/png", "size": 170855, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 170855, "w": 256}, "thumbnail_url": "mxc://matrix.org/qTfporLEnxtdkdwmPUQwWNtg", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/qTfporLEnxtdkdwmPUQwWNtg", "id": "qTfporLEnxtdkdwmPUQwWNtg"}, {"body": "Privacy Pam is Sad", "info": {"h": 256, "mimetype": "image/png", "size": 179575, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 179575, "w": 256}, "thumbnail_url": "mxc://matrix.org/MvUDjTTYKanEzFAExAhJfyAL", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/MvUDjTTYKanEzFAExAhJfyAL", "id": "MvUDjTTYKanEzFAExAhJfyAL"}, {"body": "Privacy Pam Smiles", "info": {"h": 256, "mimetype": "image/png", "size": 185764, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 185764, "w": 256}, "thumbnail_url": "mxc://matrix.org/cUbyqEDvdvxMqnfBGKmIpgfp", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/cUbyqEDvdvxMqnfBGKmIpgfp", "id": "cUbyqEDvdvxMqnfBGKmIpgfp"}, {"body": "Privacy Pam is Thinking", "info": {"h": 256, "mimetype": "image/png", "size": 199567, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 199567, "w": 256}, "thumbnail_url": "mxc://matrix.org/JnxtjVDYQHKGMWDRqSDgCwPL", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/JnxtjVDYQHKGMWDRqSDgCwPL", "id": "JnxtjVDYQHKGMWDRqSDgCwPL"}, {"body": "Privacy Pam Likes", "info": {"h": 256, "mimetype": "image/png", "size": 196924, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 196924, "w": 256}, "thumbnail_url": "mxc://matrix.org/umFLoIIzwirpWpcbnlgbtNNW", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/umFLoIIzwirpWpcbnlgbtNNW", "id": "umFLoIIzwirpWpcbnlgbtNNW"}, {"body": "Privacy Pam Winks", "info": {"h": 256, "mimetype": "image/png", "size": 167280, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 167280, "w": 256}, "thumbnail_url": "mxc://matrix.org/mehuoFXMMUdUSezwTwkkxHCB", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/mehuoFXMMUdUSezwTwkkxHCB", "id": "mehuoFXMMUdUSezwTwkkxHCB"}]}
|
1
web/packs/scalar-rabbit.json
Normal file
1
web/packs/scalar-rabbit.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"title": "Rabbit", "id": "scalar-191566", "stickers": [{"body": "Carrot", "info": {"h": 200, "mimetype": "image/png", "size": 80625, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 80625, "w": 142}, "thumbnail_url": "mxc://matrix.org/kGJDCjMOgpLmZzbgknMTUHNm", "w": 142}, "msgtype": "m.sticker", "url": "mxc://matrix.org/kGJDCjMOgpLmZzbgknMTUHNm", "id": "kGJDCjMOgpLmZzbgknMTUHNm"}, {"body": "Chef", "info": {"h": 200, "mimetype": "image/png", "size": 88633, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 88633, "w": 151}, "thumbnail_url": "mxc://matrix.org/szaTExsJurtDBUUEeHHbhyqk", "w": 151}, "msgtype": "m.sticker", "url": "mxc://matrix.org/szaTExsJurtDBUUEeHHbhyqk", "id": "szaTExsJurtDBUUEeHHbhyqk"}, {"body": "Coding", "info": {"h": 185, "mimetype": "image/png", "size": 97412, "thumbnail_info": {"h": 185, "mimetype": "image/png", "size": 97412, "w": 200}, "thumbnail_url": "mxc://matrix.org/DykipVHRXsfamLGJscNLbFAB", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/DykipVHRXsfamLGJscNLbFAB", "id": "DykipVHRXsfamLGJscNLbFAB"}, {"body": "Doctor", "info": {"h": 200, "mimetype": "image/png", "size": 113391, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 113391, "w": 184}, "thumbnail_url": "mxc://matrix.org/GEhjrKIapqcbVWKEsoDMhXeZ", "w": 184}, "msgtype": "m.sticker", "url": "mxc://matrix.org/GEhjrKIapqcbVWKEsoDMhXeZ", "id": "GEhjrKIapqcbVWKEsoDMhXeZ"}, {"body": "Driving", "info": {"h": 200, "mimetype": "image/png", "size": 77577, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 77577, "w": 156}, "thumbnail_url": "mxc://matrix.org/jxPXTKpyydzdHJkdFNZjTZrD", "w": 156}, "msgtype": "m.sticker", "url": "mxc://matrix.org/jxPXTKpyydzdHJkdFNZjTZrD", "id": "jxPXTKpyydzdHJkdFNZjTZrD"}, {"body": "Landing", "info": {"h": 200, "mimetype": "image/png", "size": 73602, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 73602, "w": 140}, "thumbnail_url": "mxc://matrix.org/sHhqkFCvSkFwtmvtETOtKnLP", "w": 140}, "msgtype": "m.sticker", "url": "mxc://matrix.org/sHhqkFCvSkFwtmvtETOtKnLP", "id": "sHhqkFCvSkFwtmvtETOtKnLP"}, {"body": "Phone", "info": {"h": 200, "mimetype": "image/png", "size": 94007, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 94007, "w": 172}, "thumbnail_url": "mxc://matrix.org/mnNNbLjjLjQIcKaybAyVMKMQ", "w": 172}, "msgtype": "m.sticker", "url": "mxc://matrix.org/mnNNbLjjLjQIcKaybAyVMKMQ", "id": "mnNNbLjjLjQIcKaybAyVMKMQ"}, {"body": "Running", "info": {"h": 157, "mimetype": "image/png", "size": 83290, "thumbnail_info": {"h": 157, "mimetype": "image/png", "size": 83290, "w": 200}, "thumbnail_url": "mxc://matrix.org/gloPNMnAwUEtrtTsaeqPTlhK", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/gloPNMnAwUEtrtTsaeqPTlhK", "id": "gloPNMnAwUEtrtTsaeqPTlhK"}, {"body": "Science", "info": {"h": 200, "mimetype": "image/png", "size": 103111, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 103111, "w": 155}, "thumbnail_url": "mxc://matrix.org/uDmIFKTXYQpzipNELqRhWSsj", "w": 155}, "msgtype": "m.sticker", "url": "mxc://matrix.org/uDmIFKTXYQpzipNELqRhWSsj", "id": "uDmIFKTXYQpzipNELqRhWSsj"}, {"body": "Work", "info": {"h": 150, "mimetype": "image/png", "size": 81850, "thumbnail_info": {"h": 150, "mimetype": "image/png", "size": 81850, "w": 200}, "thumbnail_url": "mxc://matrix.org/kYOcGZCqtNzBSUqBBOaLDBgE", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/kYOcGZCqtNzBSUqBBOaLDBgE", "id": "kYOcGZCqtNzBSUqBBOaLDBgE"}]}
|
1
web/packs/scalar-sheltie.json
Normal file
1
web/packs/scalar-sheltie.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"title": "Sheltie", "id": "scalar-192093", "stickers": [{"body": "Busy", "info": {"h": 173, "mimetype": "image/png", "size": 132161, "thumbnail_info": {"h": 173, "mimetype": "image/png", "size": 132161, "w": 200}, "thumbnail_url": "mxc://matrix.org/KbQyHYcnRFPSfRCbGqbTBiWt", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/KbQyHYcnRFPSfRCbGqbTBiWt", "id": "KbQyHYcnRFPSfRCbGqbTBiWt"}, {"body": "Confused", "info": {"h": 200, "mimetype": "image/png", "size": 111042, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 111042, "w": 173}, "thumbnail_url": "mxc://matrix.org/KkCmAbAPsgeUFdOyOceqAFBr", "w": 173}, "msgtype": "m.sticker", "url": "mxc://matrix.org/KkCmAbAPsgeUFdOyOceqAFBr", "id": "KkCmAbAPsgeUFdOyOceqAFBr"}, {"body": "Happy", "info": {"h": 158, "mimetype": "image/png", "size": 110679, "thumbnail_info": {"h": 158, "mimetype": "image/png", "size": 110679, "w": 200}, "thumbnail_url": "mxc://matrix.org/gFrdGIZbVATfwziAHnIYwEuh", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/gFrdGIZbVATfwziAHnIYwEuh", "id": "gFrdGIZbVATfwziAHnIYwEuh"}, {"body": "Hungry", "info": {"h": 183, "mimetype": "image/png", "size": 97642, "thumbnail_info": {"h": 183, "mimetype": "image/png", "size": 97642, "w": 200}, "thumbnail_url": "mxc://matrix.org/LWtWooRvIbhgLjQPPtyhWNgP", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/LWtWooRvIbhgLjQPPtyhWNgP", "id": "LWtWooRvIbhgLjQPPtyhWNgP"}, {"body": "Innocent", "info": {"h": 200, "mimetype": "image/png", "size": 107331, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 107331, "w": 186}, "thumbnail_url": "mxc://matrix.org/IItfiFhKoPieFyPLceBLcFhd", "w": 186}, "msgtype": "m.sticker", "url": "mxc://matrix.org/IItfiFhKoPieFyPLceBLcFhd", "id": "IItfiFhKoPieFyPLceBLcFhd"}, {"body": "Laughing", "info": {"h": 200, "mimetype": "image/png", "size": 118620, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 118620, "w": 194}, "thumbnail_url": "mxc://matrix.org/LxEPZAsPAfjyRfAwpYSoIxwV", "w": 194}, "msgtype": "m.sticker", "url": "mxc://matrix.org/LxEPZAsPAfjyRfAwpYSoIxwV", "id": "LxEPZAsPAfjyRfAwpYSoIxwV"}, {"body": "Sad", "info": {"h": 200, "mimetype": "image/png", "size": 104622, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 104622, "w": 177}, "thumbnail_url": "mxc://matrix.org/MjdsQxPFskrLFQfXHFuJrwbr", "w": 177}, "msgtype": "m.sticker", "url": "mxc://matrix.org/MjdsQxPFskrLFQfXHFuJrwbr", "id": "MjdsQxPFskrLFQfXHFuJrwbr"}, {"body": "Sleepy", "info": {"h": 200, "mimetype": "image/png", "size": 116609, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 116609, "w": 196}, "thumbnail_url": "mxc://matrix.org/iqEhhOuswzPITADcrmQZPxbh", "w": 196}, "msgtype": "m.sticker", "url": "mxc://matrix.org/iqEhhOuswzPITADcrmQZPxbh", "id": "iqEhhOuswzPITADcrmQZPxbh"}, {"body": "Thank-you", "info": {"h": 200, "mimetype": "image/png", "size": 109865, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 109865, "w": 160}, "thumbnail_url": "mxc://matrix.org/qnyciftjKPqVDyEIjcakwCUO", "w": 160}, "msgtype": "m.sticker", "url": "mxc://matrix.org/qnyciftjKPqVDyEIjcakwCUO", "id": "qnyciftjKPqVDyEIjcakwCUO"}, {"body": "Thumb-up", "info": {"h": 200, "mimetype": "image/png", "size": 120744, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 120744, "w": 184}, "thumbnail_url": "mxc://matrix.org/xHCAaOqwMjyJYQMHIFIgeryn", "w": 184}, "msgtype": "m.sticker", "url": "mxc://matrix.org/xHCAaOqwMjyJYQMHIFIgeryn", "id": "xHCAaOqwMjyJYQMHIFIgeryn"}]}
|
1
web/packs/scalar-smilies.json
Normal file
1
web/packs/scalar-smilies.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"title": "Smilies", "id": "scalar-192094", "stickers": [{"body": "I'm really angry!", "info": {"h": 256, "mimetype": "image/png", "size": 20840, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 20840, "w": 256}, "thumbnail_url": "mxc://matrix.org/vjgWJdgaAdPLYJMsAjbJrOIa", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/vjgWJdgaAdPLYJMsAjbJrOIa", "id": "vjgWJdgaAdPLYJMsAjbJrOIa"}, {"body": "I'm dead tired", "info": {"h": 256, "mimetype": "image/png", "size": 20143, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 20143, "w": 256}, "thumbnail_url": "mxc://matrix.org/GAZUrYmcYRtcNofjGGqAWhqI", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/GAZUrYmcYRtcNofjGGqAWhqI", "id": "GAZUrYmcYRtcNofjGGqAWhqI"}, {"body": "I'm really happy", "info": {"h": 256, "mimetype": "image/png", "size": 20509, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 20509, "w": 256}, "thumbnail_url": "mxc://matrix.org/SthCvLTenNJopFCEzEeZEwJy", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/SthCvLTenNJopFCEzEeZEwJy", "id": "SthCvLTenNJopFCEzEeZEwJy"}, {"body": "Friday I'm in love!", "info": {"h": 256, "mimetype": "image/png", "size": 20726, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 20726, "w": 256}, "thumbnail_url": "mxc://matrix.org/sCpzSdxGKNVTyyaaTtDvKIVw", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/sCpzSdxGKNVTyyaaTtDvKIVw", "id": "sCpzSdxGKNVTyyaaTtDvKIVw"}, {"body": "Show me the money!", "info": {"h": 256, "mimetype": "image/png", "size": 20852, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 20852, "w": 256}, "thumbnail_url": "mxc://matrix.org/RPsEdZjVSCdxklObGMzyeUBm", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/RPsEdZjVSCdxklObGMzyeUBm", "id": "RPsEdZjVSCdxklObGMzyeUBm"}, {"body": "I'm just sad", "info": {"h": 256, "mimetype": "image/png", "size": 22825, "thumbnail_info": {"h": 256, "mimetype": "image/png", "size": 22825, "w": 256}, "thumbnail_url": "mxc://matrix.org/RseXEsYHhkmmiGCzKYDuFyZt", "w": 256}, "msgtype": "m.sticker", "url": "mxc://matrix.org/RseXEsYHhkmmiGCzKYDuFyZt", "id": "RseXEsYHhkmmiGCzKYDuFyZt"}]}
|
1
web/packs/scalar-stakey.json
Normal file
1
web/packs/scalar-stakey.json
Normal file
File diff suppressed because one or more lines are too long
1
web/packs/scalar-stickman.json
Normal file
1
web/packs/scalar-stickman.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"title": "Stickman", "id": "scalar-192096", "stickers": [{"body": "A hastily-rendered stick figure stares at you blankly. Its arms are folded: perhaps defensively, perhaps in a half-hearted Gangnam Style.", "info": {"h": 200, "mimetype": "image/png", "size": 28154, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 28154, "w": 106}, "thumbnail_url": "mxc://matrix.org/bQGEpjrZQcWLgygXmuaJCNNA", "w": 106}, "msgtype": "m.sticker", "url": "mxc://matrix.org/bQGEpjrZQcWLgygXmuaJCNNA", "id": "bQGEpjrZQcWLgygXmuaJCNNA"}, {"body": "Question marks of varying sizes orbit a stick figure's head.", "info": {"h": 194, "mimetype": "image/png", "size": 95200, "thumbnail_info": {"h": 194, "mimetype": "image/png", "size": 95200, "w": 200}, "thumbnail_url": "mxc://matrix.org/aVdcZtGRijWluoSjCAytBHnP", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/aVdcZtGRijWluoSjCAytBHnP", "id": "aVdcZtGRijWluoSjCAytBHnP"}, {"body": "A hastily-rendered stick figure stands with arms outstretched, smiling, beneath the word 'HOORAY' in an arc above its head. The figure is smiling in celebration.", "info": {"h": 200, "mimetype": "image/png", "size": 27199, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 27199, "w": 142}, "thumbnail_url": "mxc://matrix.org/BMcDXCuQjoAaWvlPBlUjXBNa", "w": 142}, "msgtype": "m.sticker", "url": "mxc://matrix.org/BMcDXCuQjoAaWvlPBlUjXBNa", "id": "BMcDXCuQjoAaWvlPBlUjXBNa"}, {"body": "A hastily-rendered stick figure stands with arms in the air beneath three blue-and-white juggling balls apparently in motion. We cannot tell whether the figure is juggling competently or has simply thrown all three balls into the air and is awaiting the inevitable. The figure's mouth is formed into an enigmatic 'o'.", "info": {"h": 200, "mimetype": "image/png", "size": 30170, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 30170, "w": 88}, "thumbnail_url": "mxc://matrix.org/mQEotjwsEKeZivqIfZjxNfgC", "w": 88}, "msgtype": "m.sticker", "url": "mxc://matrix.org/mQEotjwsEKeZivqIfZjxNfgC", "id": "mQEotjwsEKeZivqIfZjxNfgC"}, {"body": "A hastily-rendered stick figure is thinking about lunch. Shouldn't you be thinking about lunch?", "info": {"h": 187, "mimetype": "image/png", "size": 55105, "thumbnail_info": {"h": 187, "mimetype": "image/png", "size": 55105, "w": 200}, "thumbnail_url": "mxc://matrix.org/ZHGncPEBowOpxqbVYCGbBTff", "w": 200}, "msgtype": "m.sticker", "url": "mxc://matrix.org/ZHGncPEBowOpxqbVYCGbBTff", "id": "ZHGncPEBowOpxqbVYCGbBTff"}, {"body": "A hastily-rendered stick figure stands with arms outstretched, smiling.", "info": {"h": 200, "mimetype": "image/png", "size": 26585, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 26585, "w": 138}, "thumbnail_url": "mxc://matrix.org/FGCzIxIKpswOIJCWZUWlCoKi", "w": 138}, "msgtype": "m.sticker", "url": "mxc://matrix.org/FGCzIxIKpswOIJCWZUWlCoKi", "id": "FGCzIxIKpswOIJCWZUWlCoKi"}, {"body": "A hastily-rendered stick figure stands holding a placard which reads 'I HAVE OPINIONS'. The figure's mouth is wide and angry, suggesting said opinions might not be the same as yours.", "info": {"h": 200, "mimetype": "image/png", "size": 45505, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 45505, "w": 139}, "thumbnail_url": "mxc://matrix.org/eSdUNjchqskXmCOiLgjqsakm", "w": 139}, "msgtype": "m.sticker", "url": "mxc://matrix.org/eSdUNjchqskXmCOiLgjqsakm", "id": "eSdUNjchqskXmCOiLgjqsakm"}, {"body": "A hastily-rendered stick figure stands with arms in the air. The figure's mouth is formed into an enigmatic 'o'.", "info": {"h": 200, "mimetype": "image/png", "size": 25059, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 25059, "w": 132}, "thumbnail_url": "mxc://matrix.org/puRSMGiaBfdAwYzfdHQFiJMJ", "w": 132}, "msgtype": "m.sticker", "url": "mxc://matrix.org/puRSMGiaBfdAwYzfdHQFiJMJ", "id": "puRSMGiaBfdAwYzfdHQFiJMJ"}, {"body": "A hastily-rendered stick figure has put on its robe and wizard hat.", "info": {"h": 200, "mimetype": "image/png", "size": 43579, "thumbnail_info": {"h": 200, "mimetype": "image/png", "size": 43579, "w": 108}, "thumbnail_url": "mxc://matrix.org/aplWcQPboleenDWMurAdHpHb", "w": 108}, "msgtype": "m.sticker", "url": "mxc://matrix.org/aplWcQPboleenDWMurAdHpHb", "id": "aplWcQPboleenDWMurAdHpHb"}]}
|
1
web/packs/scalar-videoplasty.json
Normal file
1
web/packs/scalar-videoplasty.json
Normal file
File diff suppressed because one or more lines are too long
1
web/res/search.svg
Normal file
1
web/res/search.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9.145 18.29c-5.042 0-9.145-4.102-9.145-9.145s4.103-9.145 9.145-9.145 9.145 4.103 9.145 9.145-4.102 9.145-9.145 9.145zm0-15.167c-3.321 0-6.022 2.702-6.022 6.022s2.702 6.022 6.022 6.022 6.023-2.702 6.023-6.022-2.702-6.022-6.023-6.022zm9.263 12.443c-.817 1.176-1.852 2.188-3.046 2.981l5.452 5.453 3.014-3.013-5.42-5.421z"/></svg>
|
After Width: | Height: | Size: 419 B |
207
web/src/index.js
207
web/src/index.js
@@ -15,14 +15,23 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import { html, render, Component } from "../lib/htm/preact.js"
|
import { html, render, Component } from "../lib/htm/preact.js"
|
||||||
import { Spinner } from "./spinner.js"
|
import { Spinner } from "./spinner.js"
|
||||||
|
import { SearchBox } from "./search-box.js"
|
||||||
import * as widgetAPI from "./widget-api.js"
|
import * as widgetAPI from "./widget-api.js"
|
||||||
import * as frequent from "./frequently-used.js"
|
import * as frequent from "./frequently-used.js"
|
||||||
|
// import GiphyAPI from "./GiphySearch.js"
|
||||||
|
|
||||||
// The base URL for fetching packs. The app will first fetch ${PACK_BASE_URL}/index.json,
|
// The base URL for fetching packs. The app will first fetch ${PACK_BASE_URL}/index.json,
|
||||||
// then ${PACK_BASE_URL}/${packFile} for each packFile in the packs object of the index.json file.
|
// then ${PACK_BASE_URL}/${packFile} for each packFile in the packs object of the index.json file.
|
||||||
const PACKS_BASE_URL = "packs"
|
const PACKS_BASE_URL = "packs"
|
||||||
|
|
||||||
|
let INDEX = `${PACKS_BASE_URL}/index.json`
|
||||||
|
const params = new URLSearchParams(document.location.search)
|
||||||
|
if (params.has('config')) {
|
||||||
|
INDEX = params.get("config")
|
||||||
|
}
|
||||||
// This is updated from packs/index.json
|
// This is updated from packs/index.json
|
||||||
let HOMESERVER_URL = "https://matrix-client.matrix.org"
|
let HOMESERVER_URL = "https://matrix-client.matrix.org"
|
||||||
|
let GIPHY_API_KEY = ""
|
||||||
|
|
||||||
const makeThumbnailURL = mxc => `${HOMESERVER_URL}/_matrix/media/r0/thumbnail/${mxc.substr(6)}?height=128&width=128&method=scale`
|
const makeThumbnailURL = mxc => `${HOMESERVER_URL}/_matrix/media/r0/thumbnail/${mxc.substr(6)}?height=128&width=128&method=scale`
|
||||||
|
|
||||||
@@ -30,19 +39,120 @@ const makeThumbnailURL = mxc => `${HOMESERVER_URL}/_matrix/media/r0/thumbnail/${
|
|||||||
// This is also used to fix scrolling to sections on Element iOS
|
// This is also used to fix scrolling to sections on Element iOS
|
||||||
const isMobileSafari = navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/)
|
const isMobileSafari = navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/)
|
||||||
|
|
||||||
export const parseQuery = str => Object.fromEntries(
|
|
||||||
str.split("&")
|
|
||||||
.map(part => part.split("="))
|
|
||||||
.map(([key, value = ""]) => [key, value]))
|
|
||||||
|
|
||||||
const supportedThemes = ["light", "dark", "black"]
|
const supportedThemes = ["light", "dark", "black"]
|
||||||
|
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
packs: [],
|
||||||
|
filtering: {
|
||||||
|
searchTerm: "",
|
||||||
|
packs: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class GiphySearchTab extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
searchTerm: "",
|
||||||
|
gifs: [],
|
||||||
|
loading: false,
|
||||||
|
GIFById: {},
|
||||||
|
};
|
||||||
|
this.handleSearchChange = this.handleSearchChange.bind(this);
|
||||||
|
this.searchGifs = this.searchGifs.bind(this);
|
||||||
|
this.handleGifClick = this.handleGifClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchGifs() {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
// const apiKey = "Gc7131jiJuvI7IdN0HZ1D7nh0ow5BU6g";
|
||||||
|
const apiKey = GIPHY_API_KEY;
|
||||||
|
const url = `https://api.giphy.com/v1/gifs/search?q=${this.state.searchTerm}&api_key=${apiKey}`;
|
||||||
|
this.setState({ loading: true });
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
this.setState({ gifs: data.data, loading: false });
|
||||||
|
data.data.forEach((jsonElement) => {
|
||||||
|
const id = jsonElement.id;
|
||||||
|
const updatedItem = {
|
||||||
|
"body": jsonElement.title,
|
||||||
|
"info": {
|
||||||
|
"h": jsonElement.images.original.height,
|
||||||
|
"w": jsonElement.images.original.width,
|
||||||
|
"size": jsonElement.images.original.size,
|
||||||
|
"mimetype": "image/gif",
|
||||||
|
"thumbnail_info": {
|
||||||
|
"h": jsonElement.images.fixed_width_still.height,
|
||||||
|
"mimetype": "image/jpg",
|
||||||
|
"size": jsonElement.images.fixed_width_still.size,
|
||||||
|
"w": jsonElement.images.fixed_width_still.width
|
||||||
|
},
|
||||||
|
"thumbnail_url": jsonElement.images.fixed_width_still.url
|
||||||
|
},
|
||||||
|
"msgtype": "m.image",
|
||||||
|
"url": jsonElement.images.original.url
|
||||||
|
};
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
GIFById: {...prevState.GIFById, [id]: updatedItem}}));
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ error: "Error fetching GIFs", loading: false });
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchChange(event) {
|
||||||
|
this.setState({ searchTerm: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGifClick(gif) {
|
||||||
|
console.log(this.state.GIFById[gif.id]);
|
||||||
|
widgetAPI.sendGIF(this.state.GIFById[gif.id]);
|
||||||
|
}
|
||||||
|
async searchGiphy(searchTerm) {
|
||||||
|
if (!searchTerm) return;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { searchTerm, gifs, loading } = this.state;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value=${searchTerm}
|
||||||
|
onInput=${this.handleSearchChange}
|
||||||
|
placeholder="Search GIFs..."
|
||||||
|
/>
|
||||||
|
<button onClick=${this.searchGifs} disabled=${loading}>Search</button>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="gifs-list" style="display: grid"> -->
|
||||||
|
<div class="pack-list">
|
||||||
|
<section class="stickerpack">
|
||||||
|
<div class="sticker-list">
|
||||||
|
${GIPHY_API_KEY !== "" && gifs.map((gif) => html`
|
||||||
|
<div class="sticker" onClick=${() => this.handleGifClick(gif)} data-gif-id=${gif.id}>
|
||||||
|
<img src=${gif.images.fixed_height.url} alt=${gif.title} class="visible" data=/>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.defaultTheme = parseQuery(location.search.substr(1)).theme
|
this.defaultTheme = params.get("theme")
|
||||||
this.state = {
|
this.state = {
|
||||||
packs: [],
|
activeTab: "stickers",
|
||||||
|
packs: defaultState.packs,
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
stickersPerRow: parseInt(localStorage.mauStickersPerRow || "4"),
|
stickersPerRow: parseInt(localStorage.mauStickersPerRow || "4"),
|
||||||
@@ -53,6 +163,7 @@ class App extends Component {
|
|||||||
stickerIDs: frequent.get(),
|
stickerIDs: frequent.get(),
|
||||||
stickers: [],
|
stickers: [],
|
||||||
},
|
},
|
||||||
|
filtering: defaultState.filtering,
|
||||||
}
|
}
|
||||||
if (!supportedThemes.includes(this.state.theme)) {
|
if (!supportedThemes.includes(this.state.theme)) {
|
||||||
this.state.theme = "light"
|
this.state.theme = "light"
|
||||||
@@ -65,6 +176,7 @@ class App extends Component {
|
|||||||
this.imageObserver = null
|
this.imageObserver = null
|
||||||
this.packListRef = null
|
this.packListRef = null
|
||||||
this.navRef = null
|
this.navRef = null
|
||||||
|
this.searchStickers = this.searchStickers.bind(this)
|
||||||
this.sendSticker = this.sendSticker.bind(this)
|
this.sendSticker = this.sendSticker.bind(this)
|
||||||
this.navScroll = this.navScroll.bind(this)
|
this.navScroll = this.navScroll.bind(this)
|
||||||
this.reloadPacks = this.reloadPacks.bind(this)
|
this.reloadPacks = this.reloadPacks.bind(this)
|
||||||
@@ -89,6 +201,28 @@ class App extends Component {
|
|||||||
localStorage.mauFrequentlyUsedStickerCache = JSON.stringify(stickers.map(sticker => [sticker.id, sticker]))
|
localStorage.mauFrequentlyUsedStickerCache = JSON.stringify(stickers.map(sticker => [sticker.id, sticker]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchStickers(e) {
|
||||||
|
const sanitizeString = s => s.toLowerCase().trim()
|
||||||
|
const searchTerm = sanitizeString(e.target.value)
|
||||||
|
|
||||||
|
const allPacks = [this.state.frequentlyUsed, ...this.state.packs]
|
||||||
|
const packsWithFilteredStickers = allPacks.map(pack => ({
|
||||||
|
...pack,
|
||||||
|
stickers: pack.stickers.filter(sticker =>
|
||||||
|
sanitizeString(sticker.body).includes(searchTerm) ||
|
||||||
|
sanitizeString(sticker.id).includes(searchTerm)
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
filtering: {
|
||||||
|
...this.state.filtering,
|
||||||
|
searchTerm,
|
||||||
|
packs: packsWithFilteredStickers.filter(({ stickers }) => !!stickers.length),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setStickersPerRow(val) {
|
setStickersPerRow(val) {
|
||||||
localStorage.mauStickersPerRow = val
|
localStorage.mauStickersPerRow = val
|
||||||
document.documentElement.style.setProperty("--stickers-per-row", localStorage.mauStickersPerRow)
|
document.documentElement.style.setProperty("--stickers-per-row", localStorage.mauStickersPerRow)
|
||||||
@@ -111,13 +245,16 @@ class App extends Component {
|
|||||||
reloadPacks() {
|
reloadPacks() {
|
||||||
this.imageObserver.disconnect()
|
this.imageObserver.disconnect()
|
||||||
this.sectionObserver.disconnect()
|
this.sectionObserver.disconnect()
|
||||||
this.setState({ packs: [] })
|
this.setState({
|
||||||
|
packs: defaultState.packs,
|
||||||
|
filtering: defaultState.filtering,
|
||||||
|
})
|
||||||
this._loadPacks(true)
|
this._loadPacks(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadPacks(disableCache = false) {
|
_loadPacks(disableCache = false) {
|
||||||
const cache = disableCache ? "no-cache" : undefined
|
const cache = disableCache ? "no-cache" : undefined
|
||||||
fetch(`${PACKS_BASE_URL}/index.json`, { cache }).then(async indexRes => {
|
fetch(INDEX, { cache }).then(async indexRes => {
|
||||||
if (indexRes.status >= 400) {
|
if (indexRes.status >= 400) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -127,9 +264,15 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
const indexData = await indexRes.json()
|
const indexData = await indexRes.json()
|
||||||
HOMESERVER_URL = indexData.homeserver_url || HOMESERVER_URL
|
HOMESERVER_URL = indexData.homeserver_url || HOMESERVER_URL
|
||||||
|
GIPHY_API_KEY = indexData.giphy_api_key || ""
|
||||||
// TODO only load pack metadata when scrolled into view?
|
// TODO only load pack metadata when scrolled into view?
|
||||||
for (const packFile of indexData.packs) {
|
for (const packFile of indexData.packs) {
|
||||||
const packRes = await fetch(`${PACKS_BASE_URL}/${packFile}`, { cache })
|
let packRes
|
||||||
|
if (packFile.startsWith("https://") || packFile.startsWith("http://")) {
|
||||||
|
packRes = await fetch(packFile, { cache })
|
||||||
|
} else {
|
||||||
|
packRes = await fetch(`${PACKS_BASE_URL}/${packFile}`, { cache })
|
||||||
|
}
|
||||||
const packData = await packRes.json()
|
const packData = await packRes.json()
|
||||||
for (const sticker of packData.stickers) {
|
for (const sticker of packData.stickers) {
|
||||||
this.stickersByID.set(sticker.id, sticker)
|
this.stickersByID.set(sticker.id, sticker)
|
||||||
@@ -220,33 +363,57 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navScroll(evt) {
|
navScroll(evt) {
|
||||||
this.navRef.scrollLeft += evt.deltaY * 12
|
this.navRef.scrollLeft += evt.deltaY
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const theme = `theme-${this.state.theme}`
|
const theme = `theme-${this.state.theme}`;
|
||||||
|
const filterActive = !!this.state.filtering.searchTerm;
|
||||||
|
const packs = filterActive
|
||||||
|
? this.state.filtering.packs
|
||||||
|
: [this.state.frequentlyUsed, ...this.state.packs];
|
||||||
|
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
return html`<main class="spinner ${theme}"><${Spinner} size=${80} green /></main>`
|
return html`<main class="spinner ${theme}"><${Spinner} size=${80} green /></main>`;
|
||||||
} else if (this.state.error) {
|
} else if (this.state.error) {
|
||||||
return html`<main class="error ${theme}">
|
return html`<main class="error ${theme}">
|
||||||
<h1>Failed to load packs</h1>
|
<h1>Failed to load packs</h1>
|
||||||
<p>${this.state.error}</p>
|
<p>${this.state.error}</p>
|
||||||
</main>`
|
</main>`;
|
||||||
} else if (this.state.packs.length === 0) {
|
} else if (this.state.packs.length === 0) {
|
||||||
return html`<main class="empty ${theme}"><h1>No packs found 😿</h1></main>`
|
return html`<main class="empty ${theme}"><h1>No packs found 😿</h1></main>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<main class="has-content ${theme}">
|
return html`<main class="has-content ${theme}">
|
||||||
|
<div class="tab-container" style="display: flex;">
|
||||||
|
<a href="#stickers" class="tab" onClick=${() => this.setState({ activeTab: "stickers" })}>Stickers</a>
|
||||||
|
<a href="#gifs" class="tab" onClick=${() => this.setState({ activeTab: "gifs" })}>GIFs</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${this.state.activeTab === "stickers" && html`
|
||||||
<nav onWheel=${this.navScroll} ref=${elem => this.navRef = elem}>
|
<nav onWheel=${this.navScroll} ref=${elem => this.navRef = elem}>
|
||||||
<${NavBarItem} pack=${this.state.frequentlyUsed} iconOverride="recent" />
|
<${NavBarItem} pack=${this.state.frequentlyUsed} iconOverride="recent" />
|
||||||
${this.state.packs.map(pack => html`<${NavBarItem} id=${pack.id} pack=${pack}/>`)}
|
${this.state.packs.map(pack => html`<${NavBarItem} id=${pack.id} pack=${pack}/>`)}
|
||||||
<${NavBarItem} pack=${{ id: "settings", title: "Settings" }} iconOverride="settings" />
|
<${NavBarItem} pack=${{ id: "settings", title: "Settings" }} iconOverride="settings" />
|
||||||
</nav>
|
</nav>
|
||||||
<div class="pack-list ${isMobileSafari ? "ios-safari-hack" : ""}" ref=${elem => this.packListRef = elem}>
|
|
||||||
<${Pack} pack=${this.state.frequentlyUsed} send=${this.sendSticker} />
|
<${SearchBox} onKeyUp=${this.searchStickers} />
|
||||||
${this.state.packs.map(pack => html`<${Pack} id=${pack.id} pack=${pack} send=${this.sendSticker} />`)}
|
<div class="pack-list ${isMobileSafari ? "ios-safari-hack" : ""}" ref=${(elem) => (this.packListRef = elem)}>
|
||||||
|
${filterActive && packs.length === 0
|
||||||
|
? html`<div class="search-empty"><h1>No stickers match your search</h1></div>`
|
||||||
|
: null}
|
||||||
|
${packs.map((pack) => html`<${Pack} id=${pack.id} pack=${pack} send=${this.sendSticker} />`)}
|
||||||
<${Settings} app=${this} />
|
<${Settings} app=${this} />
|
||||||
</div>
|
</div>
|
||||||
</main>`
|
|
||||||
|
`}
|
||||||
|
${this.state.activeTab === "gifs" && GIPHY_API_KEY !== "" && html`
|
||||||
|
<${GiphySearchTab} send=${this.sendGIF} />
|
||||||
|
`}
|
||||||
|
${this.state.activeTab === "gifs" && GIPHY_API_KEY === "" && html`
|
||||||
|
<h1><center>GIF Search is not enabled. Please enable it in the config.</center></h1>
|
||||||
|
`}
|
||||||
|
</main>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +476,7 @@ const Pack = ({ pack, send }) => html`
|
|||||||
|
|
||||||
const Sticker = ({ content, send }) => html`
|
const Sticker = ({ content, send }) => html`
|
||||||
<div class="sticker" onClick=${send} data-sticker-id=${content.id}>
|
<div class="sticker" onClick=${send} data-sticker-id=${content.id}>
|
||||||
<img data-src=${makeThumbnailURL(content.url)} alt=${content.body} />
|
<img data-src=${makeThumbnailURL(content.url)} alt=${content.body} title=${content.body} />
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
26
web/src/search-box.js
Normal file
26
web/src/search-box.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// maunium-stickerpicker - A fast and simple Matrix sticker picker widget.
|
||||||
|
// Copyright (C) 2020 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import { html } from "../lib/htm/preact.js"
|
||||||
|
|
||||||
|
export const SearchBox = ({ onKeyUp, placeholder = 'Find stickers' }) => {
|
||||||
|
const component = html`
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="text" placeholder=${placeholder} onKeyUp=${onKeyUp} />
|
||||||
|
<span class="icon icon-search" />
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
return component
|
||||||
|
}
|
@@ -13,64 +13,178 @@
|
|||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
let widgetId = null
|
|
||||||
|
|
||||||
window.onmessage = event => {
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
if (!window.parent || !event.data) {
|
const widgetId = urlParams.get('widgetId'); // if you know the widget ID, supply it.
|
||||||
return
|
console.log("Widget ID:"+widgetId);
|
||||||
}
|
const api = new mxwidgets.WidgetApi(widgetId, '*');
|
||||||
|
|
||||||
const request = event.data
|
|
||||||
if (!request.requestId || !request.widgetId || !request.action || request.api !== "toWidget") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widgetId) {
|
// Before doing anything else, request capabilities:
|
||||||
if (widgetId !== request.widgetId) {
|
api.requestCapabilities(mxwidgets.StickerpickerCapabilities);
|
||||||
return
|
api.requestCapability(mxwidgets.MatrixCapabilities.MSC4039UploadFile);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
widgetId = request.widgetId
|
|
||||||
}
|
|
||||||
|
|
||||||
let response
|
api.on("ready", () => {console.log("ready event received")});
|
||||||
|
|
||||||
if (request.action === "visibility") {
|
// Start the messaging
|
||||||
response = {}
|
api.start();
|
||||||
} else if (request.action === "capabilities") {
|
|
||||||
response = { capabilities: ["m.sticker"] }
|
|
||||||
} else {
|
|
||||||
response = { error: { message: "Action not supported" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
window.parent.postMessage({ ...request, response }, event.origin)
|
// If waitForIframeLoad is false, tell the client that we're good to go
|
||||||
}
|
//api.sendContentLoaded();
|
||||||
|
|
||||||
export function sendSticker(content){
|
export function sendSticker(content){
|
||||||
const data = {
|
const data = {
|
||||||
content: {...content},
|
content: {...content},
|
||||||
// `name` is for Element Web (and also the spec)
|
|
||||||
// Element Android uses content -> body as the name
|
|
||||||
name: content.body,
|
name: content.body,
|
||||||
|
};
|
||||||
|
// do the same thing that tulir does
|
||||||
|
delete data.content.id;
|
||||||
|
// send data
|
||||||
|
api.sendSticker(data);
|
||||||
}
|
}
|
||||||
// Custom field that stores the ID even for non-telegram stickers
|
|
||||||
delete data.content.id
|
|
||||||
|
|
||||||
// This is for Element iOS
|
/*
|
||||||
const widgetData = {
|
*export function sendGIF(content){
|
||||||
...data,
|
* // just print out content, should be URL
|
||||||
description: content.body,
|
* console.log("Content:"+content.url);
|
||||||
file: `${content.id}.png`,
|
* return new Promise((resolve, reject) => {
|
||||||
}
|
* const xhr = new XMLHttpRequest();
|
||||||
// Element iOS explodes if there are extra fields present
|
* xhr.open('GET', content.url, true);
|
||||||
delete widgetData.content["net.maunium.telegram.sticker"]
|
* xhr.onreadystatechange = function() {
|
||||||
|
* if (xhr.readyState === 4) {
|
||||||
|
* if (xhr.status === 200) {
|
||||||
|
* const responseData = xhr.responseText;
|
||||||
|
* // Call uploadFile with response data
|
||||||
|
* api.uploadFile(responseData)
|
||||||
|
* .then(result => {
|
||||||
|
* console.log("Here's the result:"+result.content_uri);
|
||||||
|
* // mess around with the content object, then send it as sticker
|
||||||
|
* content.url = result.content_uri;
|
||||||
|
* sendSticker(content);
|
||||||
|
* resolve(result);
|
||||||
|
* })
|
||||||
|
* .catch(error => {
|
||||||
|
* reject(error);
|
||||||
|
* });
|
||||||
|
* } else {
|
||||||
|
* reject(new Error('Failed to fetch data')); // Reject the outer promise if fetching data fails
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* xhr.send();
|
||||||
|
* });
|
||||||
|
*}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function sendGIF(content){
|
||||||
|
// just print content, since it's a custom type with URL
|
||||||
|
console.log("Content:"+content.url);
|
||||||
|
// use fetch because I'm on IE
|
||||||
|
const lol = await fetch(content.url);
|
||||||
|
const uri_file = await lol.blob();
|
||||||
|
// call uploadFile with this
|
||||||
|
var result = await api.uploadFile(uri_file)
|
||||||
|
console.log("Got URI:"+result.content_uri);
|
||||||
|
content.url = result.content_uri;
|
||||||
|
// get thumbnail
|
||||||
|
//const thumb_uri = await fetch(content.info.thumbnail_url)
|
||||||
|
//const thumb_file = await thumb_uri.blob();
|
||||||
|
//result = await api.uploadFile(thumb_file)
|
||||||
|
//console.log("Thumb URI:"+result.content_uri);
|
||||||
|
//content.info.thumbnail_url = result.content_uri;
|
||||||
|
// actually, just delete the thumbnail
|
||||||
|
delete content.info.thumbnail_url;
|
||||||
|
// finally, send it as sticker
|
||||||
|
sendSticker(content);
|
||||||
|
|
||||||
window.parent.postMessage({
|
|
||||||
api: "fromWidget",
|
|
||||||
action: "m.sticker",
|
|
||||||
requestId: `sticker-${Date.now()}`,
|
|
||||||
widgetId,
|
|
||||||
data,
|
|
||||||
widgetData,
|
|
||||||
}, "*")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*let widgetId = null
|
||||||
|
*
|
||||||
|
*window.onmessage = event => {
|
||||||
|
* if (!window.parent || !event.data) {
|
||||||
|
* return
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const request = event.data
|
||||||
|
* if (!request.requestId || !request.widgetId || !request.action || request.api !== "toWidget") {
|
||||||
|
* return
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* if (widgetId) {
|
||||||
|
* if (widgetId !== request.widgetId) {
|
||||||
|
* return
|
||||||
|
* }
|
||||||
|
* } else {
|
||||||
|
* widgetId = request.widgetId
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* let response
|
||||||
|
*
|
||||||
|
* if (request.action === "visibility") {
|
||||||
|
* response = {}
|
||||||
|
* } else if (request.action === "capabilities") {
|
||||||
|
* response = { capabilities: ["m.sticker", "org.matrix.msc4039.upload_file"] }
|
||||||
|
* } else {
|
||||||
|
* response = { error: { message: "Action not supported" } }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* window.parent.postMessage({ ...request, response }, event.origin)
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
*export function sendSticker(content) {
|
||||||
|
* const data = {
|
||||||
|
* content: { ...content },
|
||||||
|
* // `name` is for Element Web (and also the spec)
|
||||||
|
* // Element Android uses content -> body as the name
|
||||||
|
* name: content.body,
|
||||||
|
* }
|
||||||
|
* // Custom field that stores the ID even for non-telegram stickers
|
||||||
|
* delete data.content.id
|
||||||
|
*
|
||||||
|
* // This is for Element iOS
|
||||||
|
* const widgetData = {
|
||||||
|
* ...data,
|
||||||
|
* description: content.body,
|
||||||
|
* file: `${content.id}.png`,
|
||||||
|
* }
|
||||||
|
* // Element iOS explodes if there are extra fields present
|
||||||
|
* delete widgetData.content["net.maunium.telegram.sticker"]
|
||||||
|
*
|
||||||
|
* window.parent.postMessage({
|
||||||
|
* api: "fromWidget",
|
||||||
|
* action: "m.sticker",
|
||||||
|
* requestId: `sticker-${Date.now()}`,
|
||||||
|
* widgetId,
|
||||||
|
* data,
|
||||||
|
* widgetData,
|
||||||
|
* }, "*")
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
*export function sendGIF(content) {
|
||||||
|
* const data = {
|
||||||
|
* content: { ...content },
|
||||||
|
* name: content.body,
|
||||||
|
* msgtype: "m.image"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* delete data.content.id
|
||||||
|
* // This is for Element iOS
|
||||||
|
* const widgetData = {
|
||||||
|
* ...data,
|
||||||
|
* description: content.body,
|
||||||
|
* file: `${content.id}.png`,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* window.parent.postMessage({
|
||||||
|
* api: "fromWidget",
|
||||||
|
* action: "m.room.message",
|
||||||
|
* requestId: `gif-${Date.now()}`,
|
||||||
|
* widgetId,
|
||||||
|
* data,
|
||||||
|
* widgetData,
|
||||||
|
* }, "*")
|
||||||
|
*}
|
||||||
|
*/
|
||||||
|
@@ -1 +1,16 @@
|
|||||||
*{font-family:sans-serif}body{margin:0}h1{font-size:1rem}:root{--stickers-per-row: 4;--sticker-size: calc(100vw / var(--stickers-per-row))}main{color:var(--text-color)}main.spinner{margin-top:5rem}main.error,main.empty{margin:2rem}main.empty{text-align:center}main.has-content{position:fixed;top:0;left:0;right:0;bottom:0;display:grid;grid-template-rows:calc(12vw + 2px) auto}main.theme-light{--highlight-color: #eee;--text-color: black;background-color:white}main.theme-dark{--highlight-color: #444;--text-color: white;background-color:#22262e}main.theme-black{--highlight-color: #222;--text-color: white;background-color:black}.icon{width:100%;height:100%;background-color:var(--text-color);mask-size:contain;-webkit-mask-size:contain;mask-image:var(--icon-image);-webkit-mask-image:var(--icon-image)}.icon.icon-settings{--icon-image: url(../res/settings.svg)}.icon.icon-recent{--icon-image: url(../res/recent.svg)}nav{display:flex;overflow-x:auto}nav>a{border-bottom:2px solid transparent}nav>a.visible{border-bottom-color:green}nav>a>div.sticker{width:12vw;height:12vw}div.pack-list,nav{scrollbar-width:none}div.pack-list::-webkit-scrollbar,nav::-webkit-scrollbar{display:none}div.pack-list{overflow-y:auto}div.pack-list.ios-safari-hack{position:fixed;top:calc(12vw + 2px);bottom:0;left:0;right:0;-webkit-overflow-scrolling:touch}section.stickerpack{margin-top:.75rem}section.stickerpack>div.sticker-list{display:flex;flex-wrap:wrap}section.stickerpack>h1{margin:0 0 0 .75rem}div.sticker{display:flex;padding:4px;cursor:pointer;position:relative;width:var(--sticker-size);height:var(--sticker-size);box-sizing:border-box}div.sticker:hover{background-color:var(--highlight-color)}div.sticker>img{display:none;width:100%;object-fit:contain}div.sticker>img.visible{display:initial}div.sticker>.icon{width:70%;height:70%;margin:15%}div.settings-list{display:flex;flex-direction:column}div.settings-list>*{margin:.5rem}div.settings-list button{padding:.5rem;border-radius:.25rem}div.settings-list input{width:100%}
|
*{font-family:sans-serif}body{margin:0}h1{font-size:1rem}:root{--stickers-per-row: 4;--sticker-size: calc(100vw / var(--stickers-per-row))}main{color:var(--text-color)}main.spinner{margin-top:5rem}main.error,main.empty{margin:2rem}main.empty{text-align:center}main.has-content{position:fixed;top:0;left:0;right:0;bottom:0;display:grid;grid-template-rows:calc(12vw + 2px) min-content auto}main.theme-light{--highlight-color: #eee;--search-box-color: var(--highlight-color);--text-color: black;background-color:#fff}main.theme-dark{--highlight-color: #444;--search-box-color: #383e4b;--text-color: white;background-color:#22262e}main.theme-black{--highlight-color: #222;--search-box-color: var(--highlight-color);--text-color: white;background-color:#000}.icon{width:100%;height:100%;background-color:var(--text-color);mask-size:contain;-webkit-mask-size:contain;mask-image:var(--icon-image);-webkit-mask-image:var(--icon-image)}.icon.icon-settings{--icon-image: url(../res/settings.svg)}.icon.icon-recent{--icon-image: url(../res/recent.svg)}.icon.icon.icon-search{--icon-image: url(../res/search.svg)}nav{display:flex;overflow-x:auto}nav>a{border-bottom:2px solid transparent}nav>a.visible{border-bottom-color:green}nav>a>div.sticker{width:12vw;height:12vw}div.pack-list,nav{scrollbar-width:none}div.pack-list::-webkit-scrollbar,nav::-webkit-scrollbar{display:none}div.pack-list{overflow-y:auto}div.pack-list.ios-safari-hack{position:fixed;top:calc(calc(12vw + 2px) + calc(2 * 0.7rem + 2 * 0.5rem + 1rem));bottom:0;left:0;right:0;-webkit-overflow-scrolling:touch}div.search-empty{margin:1.2rem;text-align:center}section.stickerpack{margin-top:.75rem}section.stickerpack>div.sticker-list{display:flex;flex-wrap:wrap}section.stickerpack>h1{margin:0 0 0 .75rem}div.sticker{display:flex;padding:4px;cursor:pointer;position:relative;width:var(--sticker-size);height:var(--sticker-size);box-sizing:border-box}div.sticker:hover{background-color:var(--highlight-color)}div.sticker>img{display:none;width:100%;object-fit:contain}div.sticker>img.visible{display:initial}div.sticker>.icon{width:70%;height:70%;margin:15%}div.search-box{position:relative;display:flex}div.search-box>input[type=text]{flex-grow:1;background-color:var(--search-box-color);outline:none;border:none;border-radius:.25rem;height:1rem;padding:.7rem;padding-right:calc(1rem + 0.7rem);margin:.5rem;font-size:1rem;color:var(--text-color)}div.search-box>span.icon{display:flex;position:absolute;top:calc(50% - 1rem / 2);right:1rem;width:1rem;height:1rem;box-sizing:border-box}div.settings-list{display:flex;flex-direction:column}div.settings-list>*{margin:.5rem}div.settings-list button{padding:.5rem;border-radius:.25rem}div.settings-list input{width:100%}
|
||||||
|
|
||||||
|
a.tab {
|
||||||
|
padding: 5% 5%;
|
||||||
|
width: 40%;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
-moz-appearance: button;
|
||||||
|
appearance: button;
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
color: initial;
|
||||||
|
}
|
||||||
|
@@ -32,6 +32,12 @@ $nav-bottom-highlight: 2px
|
|||||||
$nav-height: calc(#{$nav-sticker-size} + #{$nav-bottom-highlight})
|
$nav-height: calc(#{$nav-sticker-size} + #{$nav-bottom-highlight})
|
||||||
$nav-height-inverse: calc(-#{$nav-sticker-size} - #{$nav-bottom-highlight})
|
$nav-height-inverse: calc(-#{$nav-sticker-size} - #{$nav-bottom-highlight})
|
||||||
|
|
||||||
|
$search-box-icon-size: 1rem
|
||||||
|
$search-box-input-height: 1rem
|
||||||
|
$search-box-input-padding: .7rem
|
||||||
|
$search-box-input-margin: .5rem
|
||||||
|
$search-box-height: calc(2 * #{$search-box-input-padding} + 2 * #{$search-box-input-margin} + #{$search-box-input-height})
|
||||||
|
|
||||||
main
|
main
|
||||||
color: var(--text-color)
|
color: var(--text-color)
|
||||||
|
|
||||||
@@ -50,22 +56,24 @@ main
|
|||||||
left: 0
|
left: 0
|
||||||
right: 0
|
right: 0
|
||||||
bottom: 0
|
bottom: 0
|
||||||
|
|
||||||
display: grid
|
display: grid
|
||||||
grid-template-rows: $nav-height auto
|
grid-template-rows: $nav-height min-content auto
|
||||||
|
|
||||||
main.theme-light
|
main.theme-light
|
||||||
--highlight-color: #eee
|
--highlight-color: #eee
|
||||||
|
--search-box-color: var(--highlight-color)
|
||||||
--text-color: black
|
--text-color: black
|
||||||
background-color: white
|
background-color: white
|
||||||
|
|
||||||
main.theme-dark
|
main.theme-dark
|
||||||
--highlight-color: #444
|
--highlight-color: #444
|
||||||
|
--search-box-color: #383e4b
|
||||||
--text-color: white
|
--text-color: white
|
||||||
background-color: #22262e
|
background-color: #22262e
|
||||||
|
|
||||||
main.theme-black
|
main.theme-black
|
||||||
--highlight-color: #222
|
--highlight-color: #222
|
||||||
|
--search-box-color: var(--highlight-color)
|
||||||
--text-color: white
|
--text-color: white
|
||||||
background-color: black
|
background-color: black
|
||||||
|
|
||||||
@@ -84,6 +92,9 @@ main.theme-black
|
|||||||
&.icon-recent
|
&.icon-recent
|
||||||
--icon-image: url(../res/recent.svg)
|
--icon-image: url(../res/recent.svg)
|
||||||
|
|
||||||
|
&.icon.icon-search
|
||||||
|
--icon-image: url(../res/search.svg)
|
||||||
|
|
||||||
nav
|
nav
|
||||||
display: flex
|
display: flex
|
||||||
overflow-x: auto
|
overflow-x: auto
|
||||||
@@ -109,12 +120,16 @@ div.pack-list
|
|||||||
|
|
||||||
div.pack-list.ios-safari-hack
|
div.pack-list.ios-safari-hack
|
||||||
position: fixed
|
position: fixed
|
||||||
top: $nav-height
|
top: calc(#{$nav-height} + #{$search-box-height})
|
||||||
bottom: 0
|
bottom: 0
|
||||||
left: 0
|
left: 0
|
||||||
right: 0
|
right: 0
|
||||||
-webkit-overflow-scrolling: touch
|
-webkit-overflow-scrolling: touch
|
||||||
|
|
||||||
|
div.search-empty
|
||||||
|
margin: 1.2rem
|
||||||
|
text-align: center
|
||||||
|
|
||||||
section.stickerpack
|
section.stickerpack
|
||||||
margin-top: .75rem
|
margin-top: .75rem
|
||||||
|
|
||||||
@@ -150,6 +165,32 @@ div.sticker
|
|||||||
height: 70%
|
height: 70%
|
||||||
margin: 15%
|
margin: 15%
|
||||||
|
|
||||||
|
div.search-box
|
||||||
|
position: relative
|
||||||
|
display: flex
|
||||||
|
|
||||||
|
>input[type="text"]
|
||||||
|
flex-grow: 1
|
||||||
|
background-color: var(--search-box-color)
|
||||||
|
outline: none
|
||||||
|
border: none
|
||||||
|
border-radius: .25rem
|
||||||
|
height: $search-box-input-height
|
||||||
|
padding: $search-box-input-padding
|
||||||
|
padding-right: calc(#{$search-box-icon-size} + #{$search-box-input-padding})
|
||||||
|
margin: $search-box-input-margin
|
||||||
|
font-size: 1rem
|
||||||
|
color: var(--text-color)
|
||||||
|
|
||||||
|
>span.icon
|
||||||
|
display: flex
|
||||||
|
position: absolute
|
||||||
|
top: calc(50% - #{$search-box-icon-size} / 2)
|
||||||
|
right: $search-box-icon-size
|
||||||
|
width: $search-box-icon-size
|
||||||
|
height: $search-box-icon-size
|
||||||
|
box-sizing: border-box
|
||||||
|
|
||||||
div.settings-list
|
div.settings-list
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
@@ -163,3 +204,4 @@ div.settings-list
|
|||||||
|
|
||||||
input
|
input
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
|
@@ -1 +1 @@
|
|||||||
.sk-center-wrapper{width:100%;display:flex;justify-content:space-around}.sk-chase{position:relative;animation:sk-chase 2.5s infinite linear both}.sk-chase.green>.sk-chase-dot:before{background-color:#00C853}.sk-chase>.sk-chase-dot{width:100%;height:100%;position:absolute;left:0;top:0;animation:sk-chase-dot 2.0s infinite ease-in-out both}.sk-chase>.sk-chase-dot:before{content:'';display:block;width:25%;height:25%;border-radius:100%;animation:sk-chase-dot-before 2.0s infinite ease-in-out both;background-color:#FFF}.sk-chase>.sk-chase-dot:nth-child(1){animation-delay:-1.1s}.sk-chase>.sk-chase-dot:nth-child(2){animation-delay:-1.0s}.sk-chase>.sk-chase-dot:nth-child(3){animation-delay:-0.9s}.sk-chase>.sk-chase-dot:nth-child(4){animation-delay:-0.8s}.sk-chase>.sk-chase-dot:nth-child(5){animation-delay:-0.7s}.sk-chase>.sk-chase-dot:nth-child(6){animation-delay:-0.6s}.sk-chase>.sk-chase-dot:nth-child(1):before{animation-delay:-1.1s}.sk-chase>.sk-chase-dot:nth-child(2):before{animation-delay:-1.0s}.sk-chase>.sk-chase-dot:nth-child(3):before{animation-delay:-0.9s}.sk-chase>.sk-chase-dot:nth-child(4):before{animation-delay:-0.8s}.sk-chase>.sk-chase-dot:nth-child(5):before{animation-delay:-0.7s}.sk-chase>.sk-chase-dot:nth-child(6):before{animation-delay:-0.6s}@keyframes sk-chase{100%{transform:rotate(360deg)}}@keyframes sk-chase-dot{80%,100%{transform:rotate(360deg)}}@keyframes sk-chase-dot-before{50%{transform:scale(0.4)}100%,0%{transform:scale(1)}}
|
.sk-center-wrapper{width:100%;display:flex;justify-content:space-around}.sk-chase{position:relative;animation:sk-chase 2.5s infinite linear both}.sk-chase.green>.sk-chase-dot:before{background-color:#00c853}.sk-chase>.sk-chase-dot{width:100%;height:100%;position:absolute;left:0;top:0;animation:sk-chase-dot 2s infinite ease-in-out both}.sk-chase>.sk-chase-dot:before{content:"";display:block;width:25%;height:25%;border-radius:100%;animation:sk-chase-dot-before 2s infinite ease-in-out both;background-color:#fff}.sk-chase>.sk-chase-dot:nth-child(1){animation-delay:-1.1s}.sk-chase>.sk-chase-dot:nth-child(2){animation-delay:-1s}.sk-chase>.sk-chase-dot:nth-child(3){animation-delay:-0.9s}.sk-chase>.sk-chase-dot:nth-child(4){animation-delay:-0.8s}.sk-chase>.sk-chase-dot:nth-child(5){animation-delay:-0.7s}.sk-chase>.sk-chase-dot:nth-child(6){animation-delay:-0.6s}.sk-chase>.sk-chase-dot:nth-child(1):before{animation-delay:-1.1s}.sk-chase>.sk-chase-dot:nth-child(2):before{animation-delay:-1s}.sk-chase>.sk-chase-dot:nth-child(3):before{animation-delay:-0.9s}.sk-chase>.sk-chase-dot:nth-child(4):before{animation-delay:-0.8s}.sk-chase>.sk-chase-dot:nth-child(5):before{animation-delay:-0.7s}.sk-chase>.sk-chase-dot:nth-child(6):before{animation-delay:-0.6s}@keyframes sk-chase{100%{transform:rotate(360deg)}}@keyframes sk-chase-dot{80%,100%{transform:rotate(360deg)}}@keyframes sk-chase-dot-before{50%{transform:scale(0.4)}100%,0%{transform:scale(1)}}
|
||||||
|
2366
web/yarn.lock
2366
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user