From 09dcd67a906fd2bb56786e6423784f4334c27f95 Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Sun, 16 Apr 2023 23:24:11 +0930 Subject: [PATCH 01/91] Fixing bugs with the plugin with submission. Calling the stash graphql api manually as I only care about a few fields when submitting data. Improved logging message and report % of files being processed. Use a persistent session with timestamp.trade instead of connecting for each request. --- plugins/timestampTrade/timestampTrade.py | 89 +++++++++++++++++------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index 7d4c7a5e..dca013a6 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -5,8 +5,13 @@ import sys import requests import json +import time +import math + per_page = 100 +request_s = requests.Session() + def processScene(s): if len(s['stash_ids']) > 0: @@ -19,7 +24,7 @@ def processScene(s): 'api returned something, for scene: ' + s['title'] + ' marker count: ' + str(len(md['marker']))) markers = [] for m in md['marker']: - log.debug('-- ' + m['name'] + ", " + str(m['start'] / 1000)) +# log.debug('-- ' + m['name'] + ", " + str(m['start'] / 1000)) marker = {} marker["seconds"] = m['start'] / 1000 marker["primary_tag"] = m["tag"] @@ -35,38 +40,68 @@ def processAll(): log.info('Getting scene count') count=stash.find_scenes(f={"stash_id":{"value":"","modifier":"NOT_NULL"},"has_markers":"false"},filter={"per_page": 1},get_count=True)[0] log.info(str(count)+' scenes to submit.') + i=0 for r in range(1,int(count/per_page)+1): - log.info('processing '+str(r*per_page)+ ' - '+str(count)) + log.info('fetching data: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) scenes=stash.find_scenes(f={"stash_id":{"value":"","modifier":"NOT_NULL"},"has_markers":"false"},filter={"page":r,"per_page": per_page}) for s in scenes: processScene(s) + i=i+1 + log.progress((i/count)) + time.sleep(2) def submit(): - count = stash.find_scenes(f={"has_markers": "true"}, filter={"per_page": 1}, get_count=True)[0] - for r in range(1, int(count / per_page) + 2): - log.info('processing ' + str((r - 1) * per_page) + ' - ' + str(r * per_page) + ' / ' + str(count)) - scenes = stash.find_scenes(f={"has_markers": "true"}, filter={"page": r, "per_page": per_page}) - for s in scenes: - # Cleanup, remove fields that are not needed by the api like ratings, file paths etc - for x in ['id', 'checksum', 'oshash', 'phash', 'rating', 'organized', 'o_counter', 'file','path', 'galleries']: - s.pop(x, None) - for t in s['tags']: - for x in ['id', 'image_path', 'scene_count', 'primary_tag']: - t.pop(x, None) - for t in s['performers']: - for x in ['id', 'checksum', 'scene_count', 'image_path', 'image_count', 'gallery_count', 'favorite', - 'tags']: - t.pop(x, None) - for m in s['scene_markers']: - for x in ['id', 'scene', 'tags']: - m.pop(x, None) - for x in ['id', 'aliases', 'image_path', 'scene_count']: - m['primary_tag'].pop(x, None) - - print("submitting scene: " + str(s)) - requests.post('https://timestamp.trade/submit-stash', json=s) - - + query ="""query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) { + findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) { + count + scenes { + title + details + url + date + performers{ + name + stash_ids{ + endpoint + stash_id + } + } + tags{ + name + } + studio{ + name + stash_ids{ + endpoint + stash_id + } + } + stash_ids{ + endpoint + stash_id + } + scene_markers{ + title + seconds + primary_tag{ + name + } + } + } + } + }""" + scenes = stash.call_gql(query,variables={'scene_filter':{'has_markers': 'true'},'filter':{'page':1,'per_page':1,"q":"","direction":"DESC","sort":"updated_at"}}) + count=scenes['findScenes']['count'] + i=0 + for r in range(1, math.ceil(count/per_page) + 1): + log.info('submitting scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) + scenes = stash.call_gql(query,variables={'scene_filter':{'has_markers': 'true'},'filter':{'page':r,'per_page':per_page,"q":"","direction":"DESC","sort":"updated_at"}}) + for s in scenes['findScenes']['scenes']: + log.debug("submitting scene: " + str(s)) + request_s.post('https://timestamp.trade/submit-stash', json=s) + i=i+1 + log.progress((i/count)) + time.sleep(2) From 924a5e3ea561bce212e84dca01f0282250a420d8 Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Mon, 17 Apr 2023 10:24:08 +0930 Subject: [PATCH 02/91] Changed to fragment instead of writing the query myself. --- plugins/timestampTrade/timestampTrade.py | 78 +++++++++++------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index dca013a6..633d773a 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -51,52 +51,44 @@ def processAll(): time.sleep(2) def submit(): - query ="""query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) { - findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) { - count - scenes { - title - details - url - date - performers{ - name - stash_ids{ - endpoint - stash_id - } - } - tags{ - name - } - studio{ - name - stash_ids{ - endpoint - stash_id - } - } - stash_ids{ - endpoint - stash_id - } - scene_markers{ - title - seconds - primary_tag{ - name - } - } - } - } - }""" - scenes = stash.call_gql(query,variables={'scene_filter':{'has_markers': 'true'},'filter':{'page':1,'per_page':1,"q":"","direction":"DESC","sort":"updated_at"}}) - count=scenes['findScenes']['count'] + scene_fgmt = """title + details + url + date + performers{ + name + stash_ids{ + endpoint + stash_id + } + } + tags{ + name + } + studio{ + name + stash_ids{ + endpoint + stash_id + } + } + stash_ids{ + endpoint + stash_id + } + scene_markers{ + title + seconds + primary_tag{ + name + } + }""" + count = stash.find_scenes(f={"has_markers": "true"}, filter={"per_page": 1}, get_count=True)[0] i=0 for r in range(1, math.ceil(count/per_page) + 1): log.info('submitting scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) - scenes = stash.call_gql(query,variables={'scene_filter':{'has_markers': 'true'},'filter':{'page':r,'per_page':per_page,"q":"","direction":"DESC","sort":"updated_at"}}) - for s in scenes['findScenes']['scenes']: + scenes = stash.find_scenes(f={"has_markers": "true"}, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) + for s in scenes: log.debug("submitting scene: " + str(s)) request_s.post('https://timestamp.trade/submit-stash', json=s) i=i+1 From 6fb95ada30198c7e65b501657952d2bee03c3442 Mon Sep 17 00:00:00 2001 From: alucicrazy <122958864+alucicrazy@users.noreply.github.com> Date: Sat, 4 Nov 2023 02:32:10 -0300 Subject: [PATCH 03/91] Add zip format to blocklist --- plugins/filenameParser/filenameParser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/filenameParser/filenameParser.js b/plugins/filenameParser/filenameParser.js index 999f29b2..c6e94b50 100644 --- a/plugins/filenameParser/filenameParser.js +++ b/plugins/filenameParser/filenameParser.js @@ -162,6 +162,7 @@ function cleanFilename(name) { var blockList = [ 'mp4', 'mov', + 'zip', 'xxx', '4k', '4096x2160', From 952c99ec7bdd7b2e7144872f378849e1dc42c841 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:02:57 +1100 Subject: [PATCH 04/91] Build stable and main indexes --- .github/workflows/deploy.yml | 48 +++++++++++++++++++++++++++++++ .gitignore | 1 + build_site.sh | 56 ++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100755 build_site.sh diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..1e5ed7ae --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,48 @@ +name: Deploy repository to Github Pages + +on: + push: + branches: [ main, stable ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Checkout main + uses: actions/checkout@v2 + with: + path: main + ref: main + - run: | + cd main + ./build_site.sh ../_site/develop + - name: Checkout Stable + uses: actions/checkout@v2 + with: + path: stable + ref: stable + - run: | + cd stable + ../master/build_site.sh ../_site/stable + - uses: actions/upload-pages-artifact@v1 + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-20.04 + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..16182c5d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/_site \ No newline at end of file diff --git a/build_site.sh b/build_site.sh new file mode 100755 index 00000000..d849002f --- /dev/null +++ b/build_site.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# builds a repository of scrapers +# outputs to _site with the following structure: +# index.yml +# .zip +# Each zip file contains the scraper.yml file and any other files in the same directory + +outdir="$1" +if [ -z "$outdir" ]; then + outdir="_site" +fi + +rm -rf "$outdir" +mkdir -p "$outdir" + +buildPlugin() +{ + f=$1 + # get the scraper id from the directory + plugin_id=$(basename "$f") + + echo "Processing $plugin_id" + + # create a directory for the version + version=$(git log -n 1 --pretty=format:%h -- "$f"/*) + updated=$(git log -n 1 --date="format:%F %T %z" --pretty=format:%ad -- "$f"/*) + + # create the zip file + # copy other files + zipfile=$(realpath "$outdir/$plugin_id.zip") + + pushd "$f" > /dev/null + zip -r "$zipfile" . > /dev/null + popd > /dev/null + + name=$(grep "^name:" "$f"/*.yml | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') + description=$(grep "^description:" "$f"/*.yml | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') + ymlVersion=$(grep "^version:" "$f"/*.yml | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') + version="$ymlVersion-$version" + + # write to spec index + echo "- id: $plugin_id + name: $name + metadata: + description: $description + version: $version + date: $updated + path: $plugin_id.zip + sha256: $(sha256sum "$zipfile" | cut -d' ' -f1) +" >> "$outdir"/index.yml +} + +find ./plugins -mindepth 1 -maxdepth 1 -type d | while read file; do + buildPlugin "$file" +done From 7b04605560d15f21abeca2fa42568122d6e3e4ad Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:48:16 +1100 Subject: [PATCH 05/91] Remove number from CropperJS --- plugins/{4. CropperJS => CropperJS}/CropperJS.yml | 0 plugins/{4. CropperJS => CropperJS}/cropper.css | 0 plugins/{4. CropperJS => CropperJS}/cropper.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename plugins/{4. CropperJS => CropperJS}/CropperJS.yml (100%) rename plugins/{4. CropperJS => CropperJS}/cropper.css (100%) rename plugins/{4. CropperJS => CropperJS}/cropper.js (100%) diff --git a/plugins/4. CropperJS/CropperJS.yml b/plugins/CropperJS/CropperJS.yml similarity index 100% rename from plugins/4. CropperJS/CropperJS.yml rename to plugins/CropperJS/CropperJS.yml diff --git a/plugins/4. CropperJS/cropper.css b/plugins/CropperJS/cropper.css similarity index 100% rename from plugins/4. CropperJS/cropper.css rename to plugins/CropperJS/cropper.css diff --git a/plugins/4. CropperJS/cropper.js b/plugins/CropperJS/cropper.js similarity index 100% rename from plugins/4. CropperJS/cropper.js rename to plugins/CropperJS/cropper.js From 2784391227e85a818afa30dfed2ea86304de25c2 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Wed, 22 Nov 2023 01:18:43 -0500 Subject: [PATCH 06/91] Fix deprecated configuration getting with StashAPI My bad, when I merged this into the repo on Oct 19th, I missed this deprecated stashapi call, and the stashapi change had taken effect on Oct 11th, so... bad timing. Luckily it's a one line fix. --- scripts/stash-watcher/watcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/stash-watcher/watcher.py b/scripts/stash-watcher/watcher.py index 623c7359..5eb8328f 100644 --- a/scripts/stash-watcher/watcher.py +++ b/scripts/stash-watcher/watcher.py @@ -223,7 +223,7 @@ def parseConfig(path): #If the extensions are in the config, use them. Otherwise pull them from stash. extensions = config.getlist('Config', 'Extensions') if not extensions: - stashConfig = stash.graphql_configuration() + stashConfig = stash.get_configuration() extensions = stashConfig['general']['videoExtensions'] + stashConfig['general']['imageExtensions'] + stashConfig['general']['galleryExtensions'] pollIntervalStr = config.get('Config', 'PollInterval') From a163eafe12519d0d3ad1e68b6ede86fdb4d58903 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 22 Nov 2023 18:00:28 +1100 Subject: [PATCH 07/91] Remove leading numbers --- .../stashBatchResultToggle.js | 0 .../stashBatchResultToggle.yml | 0 .../StashUserscriptLibrary.yml | 0 .../stashUserscriptLibrary.js | 0 plugins/{2. stats => stats}/stats.js | 0 plugins/{2. stats => stats}/stats.yml | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename plugins/{3. Stash Batch Result Toggle => StashBatchResultToggle}/stashBatchResultToggle.js (100%) rename plugins/{3. Stash Batch Result Toggle => StashBatchResultToggle}/stashBatchResultToggle.yml (100%) rename plugins/{1. stashUserscriptLibrary => stashUserscriptLibrary}/StashUserscriptLibrary.yml (100%) rename plugins/{1. stashUserscriptLibrary => stashUserscriptLibrary}/stashUserscriptLibrary.js (100%) rename plugins/{2. stats => stats}/stats.js (100%) rename plugins/{2. stats => stats}/stats.yml (100%) diff --git a/plugins/3. Stash Batch Result Toggle/stashBatchResultToggle.js b/plugins/StashBatchResultToggle/stashBatchResultToggle.js similarity index 100% rename from plugins/3. Stash Batch Result Toggle/stashBatchResultToggle.js rename to plugins/StashBatchResultToggle/stashBatchResultToggle.js diff --git a/plugins/3. Stash Batch Result Toggle/stashBatchResultToggle.yml b/plugins/StashBatchResultToggle/stashBatchResultToggle.yml similarity index 100% rename from plugins/3. Stash Batch Result Toggle/stashBatchResultToggle.yml rename to plugins/StashBatchResultToggle/stashBatchResultToggle.yml diff --git a/plugins/1. stashUserscriptLibrary/StashUserscriptLibrary.yml b/plugins/stashUserscriptLibrary/StashUserscriptLibrary.yml similarity index 100% rename from plugins/1. stashUserscriptLibrary/StashUserscriptLibrary.yml rename to plugins/stashUserscriptLibrary/StashUserscriptLibrary.yml diff --git a/plugins/1. stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js similarity index 100% rename from plugins/1. stashUserscriptLibrary/stashUserscriptLibrary.js rename to plugins/stashUserscriptLibrary/stashUserscriptLibrary.js diff --git a/plugins/2. stats/stats.js b/plugins/stats/stats.js similarity index 100% rename from plugins/2. stats/stats.js rename to plugins/stats/stats.js diff --git a/plugins/2. stats/stats.yml b/plugins/stats/stats.yml similarity index 100% rename from plugins/2. stats/stats.yml rename to plugins/stats/stats.yml From 7c47b63d5435e8d1c23444fedc3d382b91472281 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 22 Nov 2023 18:06:12 +1100 Subject: [PATCH 08/91] Add dependencies --- plugins/StashBatchResultToggle/stashBatchResultToggle.yml | 3 +++ plugins/sceneCoverCropper/sceneCoverCropper.yml | 3 +++ plugins/stats/stats.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/plugins/StashBatchResultToggle/stashBatchResultToggle.yml b/plugins/StashBatchResultToggle/stashBatchResultToggle.yml index 45c47492..a07558f7 100644 --- a/plugins/StashBatchResultToggle/stashBatchResultToggle.yml +++ b/plugins/StashBatchResultToggle/stashBatchResultToggle.yml @@ -1,6 +1,9 @@ name: Stash Batch Result Toggle. +# requires: StashUserscriptLibrary description: In Scene Tagger, adds button to toggle all stashdb scene match result fields. Saves clicks when you only want to save a few metadata fields. Instead of turning off every field, you batch toggle them off, then toggle on the ones you want version: 1.0 ui: + requires: + - StashUserscriptLibrary javascript: - stashBatchResultToggle.js diff --git a/plugins/sceneCoverCropper/sceneCoverCropper.yml b/plugins/sceneCoverCropper/sceneCoverCropper.yml index 735cf4a3..53f71118 100644 --- a/plugins/sceneCoverCropper/sceneCoverCropper.yml +++ b/plugins/sceneCoverCropper/sceneCoverCropper.yml @@ -1,7 +1,10 @@ name: Scene Cover Cropper +# requires: CropperJS description: Crop Scene Cover Images version: 1.0 ui: + requires: + - CropperJS css: javascript: - sceneCoverCropper.js \ No newline at end of file diff --git a/plugins/stats/stats.yml b/plugins/stats/stats.yml index f86e6a14..255ca988 100644 --- a/plugins/stats/stats.yml +++ b/plugins/stats/stats.yml @@ -1,6 +1,9 @@ name: Extended Stats +# requires: StashUserscriptLibrary description: Adds new stats to the stats page version: 1.0 ui: + requires: + - StashUserscriptLibrary javascript: - stats.js From 182b28d164592e6f42d5035d41e9f75211a6cb4f Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 22 Nov 2023 18:06:46 +1100 Subject: [PATCH 09/91] Handle dependencies --- build_site.sh | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/build_site.sh b/build_site.sh index d849002f..3cf6d2a8 100755 --- a/build_site.sh +++ b/build_site.sh @@ -18,26 +18,28 @@ buildPlugin() { f=$1 # get the scraper id from the directory - plugin_id=$(basename "$f") + dir=$(dirname "$f") + plugin_id=$(basename "$f" .yml) echo "Processing $plugin_id" # create a directory for the version - version=$(git log -n 1 --pretty=format:%h -- "$f"/*) - updated=$(git log -n 1 --date="format:%F %T %z" --pretty=format:%ad -- "$f"/*) + version=$(git log -n 1 --pretty=format:%h -- "$dir"/*) + updated=$(git log -n 1 --date="format:%F %T %z" --pretty=format:%ad -- "$dir"/*) # create the zip file # copy other files zipfile=$(realpath "$outdir/$plugin_id.zip") - pushd "$f" > /dev/null + pushd "$dir" > /dev/null zip -r "$zipfile" . > /dev/null popd > /dev/null - name=$(grep "^name:" "$f"/*.yml | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') - description=$(grep "^description:" "$f"/*.yml | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') - ymlVersion=$(grep "^version:" "$f"/*.yml | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') + name=$(grep "^name:" "$f" | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') + description=$(grep "^description:" "$f" | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') + ymlVersion=$(grep "^version:" "$f" | head -n 1 | cut -d' ' -f2- | sed -e 's/\r//' -e 's/^"\(.*\)"$/\1/') version="$ymlVersion-$version" + dep=$(grep "^# requires:" "$f" | cut -c 12- | sed -e 's/\r//') # write to spec index echo "- id: $plugin_id @@ -47,10 +49,19 @@ buildPlugin() version: $version date: $updated path: $plugin_id.zip - sha256: $(sha256sum "$zipfile" | cut -d' ' -f1) -" >> "$outdir"/index.yml + sha256: $(sha256sum "$zipfile" | cut -d' ' -f1)" >> "$outdir"/index.yml + + # handle dependencies + if [ ! -z "$dep" ]; then + echo " requires:" >> "$outdir"/index.yml + for d in ${dep//,/ }; do + echo " - $d" >> "$outdir"/index.yml + done + fi + + echo "" >> "$outdir"/index.yml } -find ./plugins -mindepth 1 -maxdepth 1 -type d | while read file; do +find ./plugins -mindepth 1 -name *.yml | while read file; do buildPlugin "$file" done From 558b3f1712fb6e8735592edf2632e255719df56e Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:41:52 +1100 Subject: [PATCH 10/91] Use UTC timestamps without timezone --- build_site.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_site.sh b/build_site.sh index 3cf6d2a8..1c9a59ad 100755 --- a/build_site.sh +++ b/build_site.sh @@ -25,7 +25,7 @@ buildPlugin() # create a directory for the version version=$(git log -n 1 --pretty=format:%h -- "$dir"/*) - updated=$(git log -n 1 --date="format:%F %T %z" --pretty=format:%ad -- "$dir"/*) + updated=$(TZ=UTC0 git log -n 1 --date="format-local:%F %T" --pretty=format:%ad -- "$dir"/*) # create the zip file # copy other files From 92b869284d046626de37bc03b9348f1384e2435b Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:50:06 +1100 Subject: [PATCH 11/91] Update deploy action --- .github/workflows/deploy.yml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1e5ed7ae..e6003e53 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,34 +15,37 @@ permissions: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout main uses: actions/checkout@v2 with: path: main ref: main + fetch-depth: '0' - run: | cd main ./build_site.sh ../_site/develop - - name: Checkout Stable - uses: actions/checkout@v2 - with: - path: stable - ref: stable - - run: | - cd stable - ../master/build_site.sh ../_site/stable - - uses: actions/upload-pages-artifact@v1 + # uncomment this once we have a stable branch + # - name: Checkout Stable + # uses: actions/checkout@v2 + # with: + # path: stable + # ref: stable + # fetch-depth: '0' + # - run: | + # cd stable + # ../master/build_site.sh ../_site/stable + # - uses: actions/upload-pages-artifact@v2 deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: build steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v2 From 79b544efe7aa6adf52f2e555434fc38eb1b8665f Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:56:24 +1100 Subject: [PATCH 12/91] Fix incorrectly commented line --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e6003e53..ba8c2b04 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,7 +36,7 @@ jobs: # - run: | # cd stable # ../master/build_site.sh ../_site/stable - # - uses: actions/upload-pages-artifact@v2 + - uses: actions/upload-pages-artifact@v2 deploy: environment: From 27b74b8d33bae211843563e33da737b6eb46eb2d Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:00:04 +1100 Subject: [PATCH 13/91] Build stable branch --- .github/workflows/deploy.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ba8c2b04..2450e61f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,15 +27,15 @@ jobs: cd main ./build_site.sh ../_site/develop # uncomment this once we have a stable branch - # - name: Checkout Stable - # uses: actions/checkout@v2 - # with: - # path: stable - # ref: stable - # fetch-depth: '0' - # - run: | - # cd stable - # ../master/build_site.sh ../_site/stable + - name: Checkout Stable + uses: actions/checkout@v2 + with: + path: stable + ref: stable + fetch-depth: '0' + - run: | + cd stable + ../main/build_site.sh ../_site/stable - uses: actions/upload-pages-artifact@v2 deploy: From 0e8f91145876276e8a0b05c8596a525d89a4f453 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:07:15 +1100 Subject: [PATCH 14/91] Use #pkgignore to omit from build script --- build_site.sh | 5 +++++ plugins/comicInfoExtractor/config.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/build_site.sh b/build_site.sh index 1c9a59ad..463ee690 100755 --- a/build_site.sh +++ b/build_site.sh @@ -17,6 +17,11 @@ mkdir -p "$outdir" buildPlugin() { f=$1 + + if grep -q "^#pkgignore" "$f"; then + return + fi + # get the scraper id from the directory dir=$(dirname "$f") plugin_id=$(basename "$f" .yml) diff --git a/plugins/comicInfoExtractor/config.yml b/plugins/comicInfoExtractor/config.yml index 51c7d1f5..235e8524 100644 --- a/plugins/comicInfoExtractor/config.yml +++ b/plugins/comicInfoExtractor/config.yml @@ -1,3 +1,4 @@ +#pkgignore #ImportList is a dictionary #that matches an xml Attribute from ComicInfo.xml to the according value in stash (using the graphql naming) #Fields that refer to different types of media are resolved by name and created if necessary (tags, studio, performers) From adc642cc7bf147375783359db447b77106102dd9 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Wed, 27 Dec 2023 17:14:29 +1100 Subject: [PATCH 15/91] Make main stable, develop a separate ref --- .github/workflows/deploy.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2450e61f..74b7f1e6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,17 +25,17 @@ jobs: fetch-depth: '0' - run: | cd main - ./build_site.sh ../_site/develop - # uncomment this once we have a stable branch - - name: Checkout Stable + ./build_site.sh ../_site/stable + - name: Checkout dev uses: actions/checkout@v2 with: - path: stable - ref: stable + path: dev + # change this ref to whatever dev branch/tag we need when necessary + ref: main fetch-depth: '0' - run: | - cd stable - ../main/build_site.sh ../_site/stable + cd dev + ../main/build_site.sh ../_site/develop - uses: actions/upload-pages-artifact@v2 deploy: From 593760e5d2adfd6eeb499ee515134febcfd0b979 Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Wed, 27 Dec 2023 23:33:18 +1030 Subject: [PATCH 16/91] Adding gallery support and fixes to allow it to work with stash 0.24. There is a new task to submit galleries and fetch metadata for untagged galleries plus a gallery update hook to update new updates. --- plugins/timestampTrade/timestampTrade.py | 340 ++++++++++++++++++++-- plugins/timestampTrade/timestampTrade.yml | 19 +- 2 files changed, 328 insertions(+), 31 deletions(-) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index 633d773a..df04aca8 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -17,40 +17,75 @@ def processScene(s): if len(s['stash_ids']) > 0: for sid in s['stash_ids']: # print('looking up markers for stash id: '+sid['stash_id']) - res = requests.post('https://timestamp.trade/get-markers/' + sid['stash_id'], json=s) - md = res.json() - if 'marker' in md: - log.info( - 'api returned something, for scene: ' + s['title'] + ' marker count: ' + str(len(md['marker']))) - markers = [] - for m in md['marker']: -# log.debug('-- ' + m['name'] + ", " + str(m['start'] / 1000)) - marker = {} - marker["seconds"] = m['start'] / 1000 - marker["primary_tag"] = m["tag"] - marker["tags"] = [] - marker["title"] = m['name'] - markers.append(marker) - if len(markers) > 0: - log.info('Saving markers') - mp.import_scene_markers(stash, markers, s['id'], 15) + res = request_s.post('https://timestamp.trade/get-markers/' + sid['stash_id'], json=s) + if res.status_code==200: + md = res.json() + if 'marker' in md: + log.info( + 'api returned something, for scene: ' + s['title'] + ' marker count: ' + str(len(md['marker']))) + markers = [] + for m in md['marker']: + # log.debug('-- ' + m['name'] + ", " + str(m['start'] / 1000)) + marker = {} + marker["seconds"] = m['start'] / 1000 + marker["primary_tag"] = m["tag"] + marker["tags"] = [] + marker["title"] = m['name'] + markers.append(marker) + if len(markers) > 0: + log.info('Saving markers') + mp.import_scene_markers(stash, markers, s['id'], 15) + if 'galleries' in md: + log.info(md['galleries']) + skip_sync_tag_id = stash.find_tag('[Timestamp: Skip Sync]', create=True).get("id") + for g in md['galleries']: + for f in g['files']: + res=stash.find_galleries(f={"checksum": {"value": f['md5'],"modifier": "EQUALS"},"tags":{"depth":0,"excludes":[skip_sync_tag_id],"modifier":"INCLUDES_ALL","value":[]}}) + for gal in res: +# log.debug('Gallery=%s' %(gal,)) + gallery={ + 'id':gal['id'], + 'title':gal['title'], + 'urls':gal['urls'], + 'date':gal['date'], + 'rating100':gal['rating100'], + 'studio_id':gal['studio']['id'], + 'performer_ids':[x['id'] for x in gal['performers']], + 'tag_ids':[x['id'] for x in gal['tags']], + 'scene_ids':[x['id'] for x in gal['scenes']], + 'details':gal['details'] + } + if len(gal['urls'])==0: + log.debug('no urls on gallery, needs new metadata') + gallery['urls'].extend([x['url'] for x in g['urls']]) + + if s['id'] not in gallery['scene_ids']: + log.debug('attaching scene %s to gallery %s '% (s['id'],gallery['id'],)) + gallery['scene_ids'].append(s['id']) + log.info('updating gallery: %s' % (gal['id'],)) + stash.update_gallery(gallery_data=gallery) + log.debug(res) + if 'movies' in md: + log.info(md['movies']) + def processAll(): log.info('Getting scene count') - count=stash.find_scenes(f={"stash_id":{"value":"","modifier":"NOT_NULL"},"has_markers":"false"},filter={"per_page": 1},get_count=True)[0] + + count=stash.find_scenes(f={"stash_id_endpoint": { "endpoint": "", "modifier": "NOT_NULL", "stash_id": ""},"has_markers":"false"},filter={"per_page": 1},get_count=True)[0] log.info(str(count)+' scenes to submit.') i=0 for r in range(1,int(count/per_page)+1): log.info('fetching data: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) - scenes=stash.find_scenes(f={"stash_id":{"value":"","modifier":"NOT_NULL"},"has_markers":"false"},filter={"page":r,"per_page": per_page}) + scenes=stash.find_scenes(f={"stash_id_endpoint": { "endpoint": "", "modifier": "NOT_NULL", "stash_id": ""},"has_markers":"false"},filter={"page":r,"per_page": per_page}) for s in scenes: processScene(s) i=i+1 log.progress((i/count)) - time.sleep(2) + time.sleep(1) -def submit(): +def submit(f={"has_markers": "true"}): scene_fgmt = """title details url @@ -82,12 +117,62 @@ def submit(): primary_tag{ name } - }""" - count = stash.find_scenes(f={"has_markers": "true"}, filter={"per_page": 1}, get_count=True)[0] + } + galleries{ + title + url + date + details + tags{ + name + } + + studio{ + name + stash_ids{ + endpoint + stash_id + } + } + performers{ + name + stash_ids{ + endpoint + stash_id + } + } + + files{ + basename + size + fingerprints{ + type + value + } + } + } + movies{ + movie{ + name + url + date + director + synopsis + studio{ + name + stash_ids{ + endpoint + stash_id + } + } + } + } + """ + count = stash.find_scenes(f=f, filter={"per_page": 1}, get_count=True,fragment=scene_fgmt)[0] i=0 for r in range(1, math.ceil(count/per_page) + 1): log.info('submitting scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) - scenes = stash.find_scenes(f={"has_markers": "true"}, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) + scenes = stash.find_scenes(f=f, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) for s in scenes: log.debug("submitting scene: " + str(s)) request_s.post('https://timestamp.trade/submit-stash', json=s) @@ -97,16 +182,215 @@ def submit(): +def submitGallery(): + scene_fgmt = """ title + url + date + details + tags{ + name + } + studio{ + name + stash_ids{ + endpoint + stash_id + } + } + performers{ + name + stash_ids{ + endpoint + stash_id + } + } + + files{ + basename + size + fingerprints{ + type + value + } + } + scenes{ + title + details + url + date + performers{ + name + stash_ids{ + endpoint + stash_id + } + } + tags{ + name + } + studio{ + name + stash_ids{ + endpoint + stash_id + } + } + stash_ids{ + endpoint + stash_id + } + }""" + + count=stash.find_galleries(f={"url": {"value": "","modifier": "NOT_NULL"}},filter={"per_page": 1},get_count=True, fragment=scene_fgmt)[0] + log.debug(count) + i = 0 + for r in range(1, math.ceil(count / per_page) + 1): + log.info('submitting gallery: %s - %s %0.1f%%' % ((r - 1) * per_page, r * per_page, (i / count) * 100,)) + galleries = stash.find_galleries(f={"url": {"value": "", "modifier": "NOT_NULL"}}, filter={"page": r, "per_page": per_page,'sort': 'created_at', 'direction': 'DESC'}, fragment=scene_fgmt) + for g in galleries: + log.debug("submitting gallery: %s" %(g,)) + request_s.post('https://timestamp.trade/submit-stash-gallery', json=g) + i = i + 1 + log.progress((i / count)) + time.sleep(2) + +def processGalleries(): + skip_sync_tag_id = stash.find_tag('[Timestamp: Skip Sync]', create=True).get("id") + + count=get_count = stash.find_galleries(f={"url": {"value": "", "modifier": "IS_NULL"}, + "tags": {"depth": 0, "excludes": [skip_sync_tag_id], "modifier": "INCLUDES_ALL", + "value": []}},filter={"per_page": 1},get_count=True)[0] + tag_cache={} + + log.info('count %s '% (count,)) + i=0 + for r in range(1, math.ceil(count/per_page) + 1): + log.info('processing gallery scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) + galleries = stash.find_galleries(f={"url": {"value": "", "modifier": "IS_NULL"}, + "tags": {"depth": 0, "excludes": [skip_sync_tag_id], "modifier": "INCLUDES_ALL", + "value": []}}, + filter={"page": r, "per_page": 20,'sort': 'created_at', 'direction': 'DESC'}) + for gal in galleries: + processGallery(gal) +def processGallery(gallery): + # ignore galleries with a url + if len(gallery['urls']) ==0: + for f in gallery['files']: + for fp in f['fingerprints']: + if fp['type']=='md5': + log.debug('looking up galleries by file hash: %s ' % (fp['value'],)) + res = request_s.post('https://timestamp.trade/gallery-md5/' + fp['value']) + if res.status_code == 200: + for g in res.json(): + log.debug('stash gallery=%s' % (gallery,)) + log.debug('tt gallery=%s' % (g,)) + + new_gallery={ + 'id':gallery['id'], + 'title':g['title'], + 'urls':[x['url'] for x in g['urls']], + 'date':g['release_date'], + 'rating100':gallery['rating100'], + 'studio_id':None, + 'performer_ids':[], + 'tag_ids':[], + 'scene_ids':[], + 'details':g['description'] + } + for p in g['performers']: + performer_id=None + # try looking up stashid + for sid in p['stash_ids']: + performers=stash.find_performers(f={"stash_id_endpoint": {"endpoint": sid['endpoint'],"stash_id": sid['stash_id'],"modifier": "EQUALS"}}) + if len(performers) >0: + performer_id=performers[0]['id'] + log.debug('performer matched %s' % (performer_id,)) + break + # look for the performer + if not performer_id: + performers=stash.find_performers(q=p['name']) + for perf in performers: + if p['name'].lower() ==perf['name'].lower(): + performer_id=perf['id'] + for pa in perf['alias_list']: + if p['name'].lower()==pa.lower(): + performer_id=perf['id'] + log.debug('match alias') + + # performer does not exist, create the performer + if not performer_id: + log.info('performer %s does not exist, creating' % (p['name'],)) + new_perf=stash.create_performer(performer_in={'name':p['name'],'stash_ids':p['stash_ids']}) + performer_id=new_perf['id'] + log.debug(new_perf) + new_gallery['performer_ids'].append(performer_id) + log.debug(performer_id) + + for tag in g['tags']: + new_gallery['tag_ids'].append(stash.find_tag(tag['name'], create=True).get("id")) + for sid in g['studio']['stash_ids']: + stud=stash.find_studios(f={"stash_id_endpoint": {"endpoint": sid['endpoint'],"stash_id": sid['stash_id'],"modifier": "EQUALS"}}) + if len(stud) >0: + new_gallery['studio_id']=stud[0]['id'] + break + if new_gallery['studio_id'] is None: + stud=stash.find_studios(q=g['studio']['name']) + for s in stud: + if s['name'].lower() == g['studio']['name'].lower(): + new_gallery['studio_id'] = s['id'] + break + for sa in s['aliases']: + if sa['name'].lower() == g['studio']['name'].lower(): + new_gallery['studio_id'] = s['id'] + break + + log.debug(new_gallery) + + + stash.update_gallery(gallery_data=new_gallery) + time.sleep(1) + + else: + log.debug('bad response from api') + time.sleep(10) + + +def getImages(gallery_id): + images=[] + res=stash.find_images(f={ "galleries": {"value": [gallery_id],"modifier": "INCLUDES_ALL"}}) + for r in res: + print(r) + img={'title':r['title'],"filename":r['visual_files'][0]['basename'],'size':r['visual_files'][0]['size'],'width':r['visual_files'][0]['width'],'height':r['visual_files'][0]['height'],'md5':r['visual_files'][0]['fingerprints'][0]['value']} + images.append(img) + print(img) + print(images) + json_input = json.loads(sys.stdin.read()) + FRAGMENT_SERVER = json_input["server_connection"] stash = StashInterface(FRAGMENT_SERVER) if 'mode' in json_input['args']: PLUGIN_ARGS = json_input['args']["mode"] - if 'submit' in PLUGIN_ARGS: + if 'submitScene' in PLUGIN_ARGS: submit() - elif 'process' in PLUGIN_ARGS: + elif 'submitGallery' in PLUGIN_ARGS: + submitGallery() + elif 'processGallery' in PLUGIN_ARGS: + processGalleries() + elif 'processScene' in PLUGIN_ARGS: processAll() + elif '2tmp' in PLUGIN_ARGS: + submit(f={"url": {"modifier": "INCLUDES","value": "sexlikereal.com"}}) + elif 'tmp' in PLUGIN_ARGS: + tmp=getImages(gallery_id=5904) + print(tmp) + elif 'hookContext' in json_input['args']: id=json_input['args']['hookContext']['id'] - scene=stash.find_scene(id) - processScene(scene) + if json_input['args']['hookContext']['type']=='Scene.Update.Post': + scene=stash.find_scene(id) + processScene(scene) + if json_input['args']['hookContext']['type']=='Gallery.Update.Post': + gallery=stash.find_gallery(id) + processGallery(gallery) + diff --git a/plugins/timestampTrade/timestampTrade.yml b/plugins/timestampTrade/timestampTrade.yml index e30fa1e6..1e015691 100644 --- a/plugins/timestampTrade/timestampTrade.yml +++ b/plugins/timestampTrade/timestampTrade.yml @@ -1,6 +1,6 @@ name: Timestamp Trade description: Sync Markers with timestamp.trade, a new database for sharing markers. -version: 0.1 +version: 0.4 url: https://github.com/stashapp/CommunityScripts/ exec: - python @@ -11,12 +11,25 @@ hooks: description: Makes Markers checking against timestamp.trade triggeredBy: - Scene.Update.Post + - name: Gallery lookup + description: Look up gallery metadata from timestamp.trade + triggeredBy: + - Gallery.Create.Post + tasks: - name: 'Submit' description: Submit markers to timestamp.trade defaultArgs: - mode: submit + mode: submitScene - name: 'Sync' description: Get markers for all scenes with a stashid defaultArgs: - mode: process + mode: processScene + - name: 'Submit Gallery' + description: Submit gallery info to timestamp.trade + defaultArgs: + mode: submitGallery + - name: 'Sync Gallery' + description: get gallery info from timestamp.trade + defaultArgs: + mode: processGallery From b87b9f6c13cc9fadcbcf6bdafb64822d5d4107b7 Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Thu, 28 Dec 2023 00:08:45 +1030 Subject: [PATCH 17/91] Cleanup --- plugins/timestampTrade/timestampTrade.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index 4d2e9fc5..c8c10cee 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -96,7 +96,7 @@ def processAll(): log.progress((i/count)) time.sleep(1) -def submit(): +def submitScene(): scene_fgmt = """title details url @@ -183,11 +183,11 @@ def submit(): skip_submit_tag_id = stash.find_tag('[Timestamp: Skip Submit]', create=True).get("id") - count = stash.find_scenes(f={"has_markers": "true","tags":{"depth":0,"excludes":[skip_sync_tag_id],"modifier":"INCLUDES_ALL","value":[]}}, filter={"per_page": 1}, get_count=True)[0] + count = stash.find_scenes(f={"has_markers": "true","tags":{"depth":0,"excludes":[skip_submit_tag_id],"modifier":"INCLUDES_ALL","value":[]}}, filter={"per_page": 1}, get_count=True)[0] i=0 for r in range(1, math.ceil(count/per_page) + 1): log.info('submitting scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) - scenes = stash.find_scenes(f={"has_markers": "true"}, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) + scenes = stash.find_scenes(f={"has_markers": "true","tags":{"depth":0,"excludes":[skip_submit_tag_id],"modifier":"INCLUDES_ALL","value":[]}}, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) for s in scenes: log.debug("submitting scene: " + str(s)) request_s.post('https://timestamp.trade/submit-stash', json=s) @@ -255,12 +255,13 @@ def submitGallery(): } }""" - count=stash.find_galleries(f={"url": {"value": "","modifier": "NOT_NULL"}},filter={"per_page": 1},get_count=True, fragment=scene_fgmt)[0] + skip_submit_tag_id = stash.find_tag('[Timestamp: Skip Submit]', create=True).get("id") + count=stash.find_galleries(f={"url": {"value": "","modifier": "NOT_NULL"},"tags":{"depth":0,"excludes":[skip_submit_tag_id],"modifier":"INCLUDES_ALL","value":[]}},filter={"per_page": 1},get_count=True, fragment=scene_fgmt)[0] log.debug(count) i = 0 for r in range(1, math.ceil(count / per_page) + 1): log.info('submitting gallery: %s - %s %0.1f%%' % ((r - 1) * per_page, r * per_page, (i / count) * 100,)) - galleries = stash.find_galleries(f={"url": {"value": "", "modifier": "NOT_NULL"}}, filter={"page": r, "per_page": per_page,'sort': 'created_at', 'direction': 'DESC'}, fragment=scene_fgmt) + galleries = stash.find_galleries(f={"url": {"value": "", "modifier": "NOT_NULL"},"tags":{"depth":0,"excludes":[skip_submit_tag_id],"modifier":"INCLUDES_ALL","value":[]}}, filter={"page": r, "per_page": per_page,'sort': 'created_at', 'direction': 'DESC'}, fragment=scene_fgmt) for g in galleries: log.debug("submitting gallery: %s" %(g,)) request_s.post('https://timestamp.trade/submit-stash-gallery', json=g) @@ -386,18 +387,13 @@ def getImages(gallery_id): if 'mode' in json_input['args']: PLUGIN_ARGS = json_input['args']["mode"] if 'submitScene' in PLUGIN_ARGS: - submit() + submitScene() elif 'submitGallery' in PLUGIN_ARGS: submitGallery() elif 'processGallery' in PLUGIN_ARGS: processGalleries() elif 'processScene' in PLUGIN_ARGS: processAll() - elif '2tmp' in PLUGIN_ARGS: - submit(f={"url": {"modifier": "INCLUDES","value": "sexlikereal.com"}}) - elif 'tmp' in PLUGIN_ARGS: - tmp=getImages(gallery_id=5904) - print(tmp) elif 'hookContext' in json_input['args']: id=json_input['args']['hookContext']['id'] From 3a51407e59ae3b1ff0db2deb26cebbb005271000 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Thu, 28 Dec 2023 21:07:23 -0500 Subject: [PATCH 18/91] Update README.md to being the process of revising for v24 --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 12cd7890..cb61deaf 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,31 @@ # CommunityScripts Repository -This repository contains plugin and utility scripts created by the Stash community and hosted on the official GitHub repo. There is also [a list of third-party plugins on our wiki page](https://github.com/stashapp/stash/wiki/Plugins-&--Scripts). +This repository contains plugin and utility scripts created by the Stash community and hosted on the official GitHub repo. -## How To Install -To download a plugin, either clone the git repo, or download the files directly. +There is also [a list of third-party plugins on our wiki page](https://github.com/stashapp/stash/wiki/Plugins-&--Scripts). -It is recommended that plugins are placed in their own subdirectory of your `plugins` directory. The `plugins` directory should be created as a subdirectory in the directory containing your `config.yml` file. This will be in `$HOME/.stash` by default. +## Please note: V24 now uses an installer, so we recommend you use that to install plugins. +Manual installs are discouraged, and you shouldn't do so unless you know what you are doing otherwise. -When downloading directly click on the file you want and then make sure to click the raw button: +## How To Install +To download a plugin in Stash v24, the CommunityScripts repo source is automatically installed by default. +This source is located at https://stashapp.github.io/CommunityScripts/stable/index.yml -![](https://user-images.githubusercontent.com/1358708/82524777-cd4cfe80-9afd-11ea-808d-5ea7bf26704f.jpg) +# Plugin, Themes, and Scripts Directory +We used to list all plugins, themes, and scripts in this repository... +but with the changes in v24, ANY items installable by the plugin installer are no longer listed here. +Use the Plugin Installer built into Stash. -# Plugin and Script Directory -This list keeps track of scripts and plugins in this repository. Please ensure the list is kept in alphabetical order. +We will continue to list the items NOT otherwise installable this way below. ## NOTE: BREAKING CHANGES -The upcoming v24 release (and the current development branch) have breaking changes to schema, and also plugin changes. -We're beginning to review plugins and the rest and patch them to work, but it's an ongoing process. -We'll update the table below as we do this, but we STRONGLY recommend you do not use the development branch unless you are prepared to help with the patching. +The recent v24 release (and development branches) had major breaking changes to old schema, and plugin changes. +We're beginning to review plugins and the rest and patch them to work, but it's an ongoing process... + +We'll update the table below as we do this... We will also be rearranging things a bit, and updating documentation (including this page) -## Plugins +## Plugins and Themes are no longer listed here. Category|Triggers|Plugin Name|Description|Minimum Stash version|Updated for v24| --------|-----------|-----------|-----------|---------------------|----- @@ -42,12 +47,7 @@ Theme Name|Description |Updated for v24| ## Utility Scripts -|Category|Userscript Name|Description|Updated for v24| +|Category|Name|Description|Updated for v24| ---------|---------------|-----------|---- StashDB |[StashDB Submission Helper](/userscripts/StashDB_Submission_Helper)|Adds handy functions for StashDB submissions like buttons to add aliases in bulk to a performer|:x: - -## Utility Scripts - -Category|Plugin Name|Description|Minimum Stash version|Updated for v24| ---------|-----------|-----------|---------------------|---- Kodi|[Kodi Helper](scripts/kodi-helper)|Generates `nfo` and `strm` for use with Kodi.|v0.7|:x: From 36556be0e581e7db6ddec6b531cf1fb388952f39 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Thu, 28 Dec 2023 21:27:49 -0500 Subject: [PATCH 19/91] small updated text --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cb61deaf..8b3740f2 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,14 @@ This repository contains plugin and utility scripts created by the Stash communi There is also [a list of third-party plugins on our wiki page](https://github.com/stashapp/stash/wiki/Plugins-&--Scripts). -## Please note: V24 now uses an installer, so we recommend you use that to install plugins. -Manual installs are discouraged, and you shouldn't do so unless you know what you are doing otherwise. +## Please note: V24 now uses an installer +# We recommend you use that to install plugins. +Manual installs are discouraged, and you shouldn't do so unless you otherwise know what you are doing. ## How To Install To download a plugin in Stash v24, the CommunityScripts repo source is automatically installed by default. -This source is located at https://stashapp.github.io/CommunityScripts/stable/index.yml + +This default source is located at https://stashapp.github.io/CommunityScripts/stable/index.yml # Plugin, Themes, and Scripts Directory We used to list all plugins, themes, and scripts in this repository... From fd2aeba1165ee9036a57c04cce3c72dc3a2cc218 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Thu, 28 Dec 2023 23:23:58 -0500 Subject: [PATCH 20/91] Fixes spacing to close #196 --- scripts/stash-watcher/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/stash-watcher/README.md b/scripts/stash-watcher/README.md index 8dc518c6..4c361eaa 100644 --- a/scripts/stash-watcher/README.md +++ b/scripts/stash-watcher/README.md @@ -39,14 +39,14 @@ There is currently no published docker image, so you'll have to build it yoursel version: "3.4" services: stash-watcher: - container_name: stash-watcher - build: - volumes: - #This is only required if you have to modify config.toml (if the defaults are fine you don't have to map this file) - - ./config.toml:/config.toml:ro - #This is the path to your stash content. If you have multiple paths, map them here - - /stash:/data:ro - restart: unless-stopped + container_name: stash-watcher + build: + volumes: + #This is only required if you have to modify config.toml (if the defaults are fine you don't have to map this file) + - ./config.toml:/config.toml:ro + #This is the path to your stash content. If you have multiple paths, map them here + - /stash:/data:ro + restart: unless-stopped ``` Then you can run From be5a4dcd7bdb957b7423bc679283ead948e403cc Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 11:48:07 -0500 Subject: [PATCH 21/91] v2.5.0 - limited changes to work with Stash v24 - STOPGAP RELEASE --- plugins/renamerOnUpdate/README.md | 16 ++++++++-- plugins/renamerOnUpdate/renamerOnUpdate.py | 35 +++++++++++---------- plugins/renamerOnUpdate/renamerOnUpdate.yml | 2 +- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/plugins/renamerOnUpdate/README.md b/plugins/renamerOnUpdate/README.md index 8eaa7c3a..08dd0ede 100644 --- a/plugins/renamerOnUpdate/README.md +++ b/plugins/renamerOnUpdate/README.md @@ -1,6 +1,17 @@ # *renamerOnUpdate* Using metadata from your Stash to rename/move your file. +# WARNING - This is a stopgap release +It is labeled 2.5.0, to fill the gap until v3.0 is released +It is NOT heavily tested, with the bare minimum needed to work with Stash v24 +Version 3 is a major rewrite, but since it's not quite ready, pushing 2.5.0 seems necessary +It worked for me, using my old config, and I got no errors during my limited tests. +YOU HAVE BEEN WARNED - any potential data loss is on you. +BEFORE YOU USE IT TO MOVE/RENAME ANYTHING CRITICAL, test it yourself. +I still suggest you wait for v3, but... if you can't, this is now out. + +All credit to Belley for all of his work over the years... and v3 is coming soon + ## Table of Contents - [*renamerOnUpdate*](#renameronupdate) @@ -33,10 +44,9 @@ Using metadata from your Stash to rename/move your file. - [*performer_limit*](#performer_limit) # Requirement -- Stash (v0.15+) -- Python 3.6+ (Tested on Python v3.9.1 64bit, Win10) +- Stash (v0.24+) +- Python 3.6+ (Tested LIGHTLY on Python v3.11 Linux) - Request Module (https://pypi.org/project/requests/) -- Tested on Windows 10/Synology/docker. # Installation diff --git a/plugins/renamerOnUpdate/renamerOnUpdate.py b/plugins/renamerOnUpdate/renamerOnUpdate.py index cf145a62..9dab0410 100644 --- a/plugins/renamerOnUpdate/renamerOnUpdate.py +++ b/plugins/renamerOnUpdate/renamerOnUpdate.py @@ -106,11 +106,9 @@ def graphql_getScene(scene_id): } fragment SceneData on Scene { id - oshash - checksum title date - rating + rating100 stash_ids { endpoint stash_id @@ -133,7 +131,7 @@ def graphql_getScene(scene_id): name gender favorite - rating + rating100 stash_ids{ endpoint stash_id @@ -168,11 +166,9 @@ def graphql_findScene(perPage, direc="DESC") -> dict: } fragment SlimSceneData on Scene { id - oshash - checksum title date - rating + rating100 organized stash_ids { endpoint @@ -196,7 +192,7 @@ def graphql_findScene(perPage, direc="DESC") -> dict: name gender favorite - rating + rating100 stash_ids{ endpoint stash_id @@ -467,7 +463,7 @@ def extract_info(scene: dict, template: None): # note: basename contains the extension scene_information['current_filename'] = os.path.basename(scene_information['current_path']) scene_information['current_directory'] = os.path.dirname(scene_information['current_path']) - scene_information['oshash'] = scene['oshash'] + scene_information['oshash'] = scene.get("oshash") scene_information['checksum'] = scene.get("checksum") scene_information['studio_code'] = scene.get("code") @@ -506,8 +502,8 @@ def extract_info(scene: dict, template: None): scene_information['duration'] = str(scene_information['duration']) # Grab Rating - if scene.get("rating"): - scene_information['rating'] = RATING_FORMAT.format(scene['rating']) + if scene.get("rating100"): + scene_information['rating'] = RATING_FORMAT.format(scene['rating100']) # Grab Performer scene_information['performer_path'] = None @@ -527,10 +523,10 @@ def extract_info(scene: dict, template: None): if "inverse_performer" in template["path"]["option"]: perf["name"] = re.sub(r"([a-zA-Z]+)(\s)([a-zA-Z]+)", r"\3 \1", perf["name"]) perf_list.append(perf['name']) - if perf.get('rating'): - if perf_rating.get(str(perf['rating'])) is None: - perf_rating[str(perf['rating'])] = [] - perf_rating[str(perf['rating'])].append(perf['name']) + if perf.get('rating100'): + if perf_rating.get(str(perf['rating100'])) is None: + perf_rating[str(perf['rating100'])] = [] + perf_rating[str(perf['rating100'])].append(perf['name']) else: perf_rating["0"].append(perf['name']) if perf.get('favorite'): @@ -630,7 +626,7 @@ def extract_info(scene: dict, template: None): scene_information['tags'] = TAGS_SPLITCHAR.join(tag_list) # Grab Height (720p,1080p,4k...) - scene_information['bitrate'] = str(round(int(scene['file']['bitrate']) / 1000000, 2)) + scene_information['bit_rate'] = str(round(int(scene['file']['bit_rate']) / 1000000, 2)) scene_information['resolution'] = 'SD' scene_information['height'] = f"{scene['file']['height']}p" if scene['file']['height'] >= 720: @@ -1068,10 +1064,12 @@ def renamer(scene_id, db_conn=None): stash_scene["oshash"] = f["oshash"] if f.get("md5"): stash_scene["checksum"] = f["md5"] + if f.get("checksum"): + stash_scene["checksum"] = f["checksum"] stash_scene["path"] = scene_file["path"] stash_scene["file"] = scene_file if scene_file.get("bit_rate"): - stash_scene["file"]["bitrate"] = scene_file["bit_rate"] + stash_scene["file"]["bit_rate"] = scene_file["bit_rate"] if scene_file.get("frame_rate"): stash_scene["file"]["framerate"] = scene_file["frame_rate"] @@ -1318,6 +1316,9 @@ def exit_plugin(msg=None, err=None): frame_rate duration bit_rate + phash: fingerprint(type: "phash") + oshash: fingerprint(type: "oshash") + checksum: fingerprint(type: "checksum") fingerprints { type value diff --git a/plugins/renamerOnUpdate/renamerOnUpdate.yml b/plugins/renamerOnUpdate/renamerOnUpdate.yml index 9a2d6b0f..f1381fe0 100644 --- a/plugins/renamerOnUpdate/renamerOnUpdate.yml +++ b/plugins/renamerOnUpdate/renamerOnUpdate.yml @@ -1,7 +1,7 @@ name: renamerOnUpdate description: Rename/move filename based on a template. url: https://github.com/stashapp/CommunityScripts -version: 2.4.4 +version: 2.5.0 exec: - python - "{pluginDir}/renamerOnUpdate.py" From 499004399d2a5391ae9e614dd2a0b0a7c89639e6 Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 12:28:35 -0500 Subject: [PATCH 22/91] remove GH Scraper Checker (obsoleted), and update README.md for that and some cleanup --- README.md | 17 +- .../GHScraper_Checker/GHScraper_Checker.py | 208 ------------------ .../GHScraper_Checker/GHScraper_Checker.yml | 21 -- plugins/GHScraper_Checker/log.py | 52 ----- 4 files changed, 8 insertions(+), 290 deletions(-) delete mode 100644 plugins/GHScraper_Checker/GHScraper_Checker.py delete mode 100644 plugins/GHScraper_Checker/GHScraper_Checker.yml delete mode 100644 plugins/GHScraper_Checker/log.py diff --git a/README.md b/README.md index 8b3740f2..8fc2cef6 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ This repository contains plugin and utility scripts created by the Stash communi There is also [a list of third-party plugins on our wiki page](https://github.com/stashapp/stash/wiki/Plugins-&--Scripts). ## Please note: V24 now uses an installer -# We recommend you use that to install plugins. -Manual installs are discouraged, and you shouldn't do so unless you otherwise know what you are doing. +# We recommend you use that to install (and update) plugins. +Manual installs are not recommended, and you shouldn't do so unless you otherwise know what you are doing. ## How To Install To download a plugin in Stash v24, the CommunityScripts repo source is automatically installed by default. @@ -14,25 +14,24 @@ To download a plugin in Stash v24, the CommunityScripts repo source is automatic This default source is located at https://stashapp.github.io/CommunityScripts/stable/index.yml # Plugin, Themes, and Scripts Directory -We used to list all plugins, themes, and scripts in this repository... -but with the changes in v24, ANY items installable by the plugin installer are no longer listed here. +We used to list all community supported plugins, themes, and scripts in this repository... +but with the changes in v24, ANY items installable by the plugin installer will no longer listed here. Use the Plugin Installer built into Stash. -We will continue to list the items NOT otherwise installable this way below. +We will continue to list the items NOT otherwise installable in this way below. ## NOTE: BREAKING CHANGES -The recent v24 release (and development branches) had major breaking changes to old schema, and plugin changes. +The recent v24 release (and future development branches) had major breaking changes to old schema and plugin changes. We're beginning to review plugins and the rest and patch them to work, but it's an ongoing process... We'll update the table below as we do this... We will also be rearranging things a bit, and updating documentation (including this page) -## Plugins and Themes are no longer listed here. +## Plugins and Themes will no longer be listed here. Category|Triggers|Plugin Name|Description|Minimum Stash version|Updated for v24| --------|-----------|-----------|-----------|---------------------|----- -Scraper|Task|[GHScraper_Checker](plugins/GHScraper_Checker)|Compare local file against github file from the community scraper repo.|v0.8|:x: -Maintenance|Task
Scene.Update|[renamerOnUpdate](plugins/renamerOnUpdate)|Rename/Move your file based on Stash metadata.|v0.7|:x: +Maintenance|Task
Scene.Update|[renamerOnUpdate](plugins/renamerOnUpdate)|Rename/Move your file based on Stash metadata.|v2.4|:white_check_mark: STOPGAP Maintenance|Set Scene Cover|[setSceneCoverFromFile](plugins/setSceneCoverFromFile)|Searchs Stash for Scenes with a cover image in the same folder and sets the cover image in stash to that image|v0.7|:x: Scenes|SceneMarker.Create
SceneMarker.Update|[markerTagToScene](plugins/markerTagToScene)|Adds primary tag of Scene Marker to the Scene on marker create/update.|v0.8 ([46bbede](https://github.com/stashapp/stash/commit/46bbede9a07144797d6f26cf414205b390ca88f9))|:x: Scanning|Scene.Create
Gallery.Create
Image.Create|[defaultDataForPath](plugins/defaultDataForPath)|Adds configured Tags, Performers and/or Studio to all newly scanned Scenes, Images and Galleries..|v0.8|:x: diff --git a/plugins/GHScraper_Checker/GHScraper_Checker.py b/plugins/GHScraper_Checker/GHScraper_Checker.py deleted file mode 100644 index b1e08f66..00000000 --- a/plugins/GHScraper_Checker/GHScraper_Checker.py +++ /dev/null @@ -1,208 +0,0 @@ -import json -import os -import re -import sys -import zipfile -from datetime import datetime - -import requests - -import log - -FRAGMENT = json.loads(sys.stdin.read()) -FRAGMENT_SERVER = FRAGMENT["server_connection"] -FRAGMENT_ARG = FRAGMENT['args']['mode'] -log.LogDebug("Starting Plugin: Github Scraper Checker") - -CHECK_LOG = False -GET_NEW_FILE = False -OVERWRITE = False - -if FRAGMENT_ARG == "CHECK": - CHECK_LOG = True -if FRAGMENT_ARG == "NEWFILE": - GET_NEW_FILE = True -if FRAGMENT_ARG == "OVERWRITE": - OVERWRITE = True - -# Don't write in log if the file don't exist locally. -IGNORE_MISS_LOCAL = False - -def graphql_getScraperPath(): - query = """ - query Configuration { - configuration { - general { - scrapersPath - } - } - } - """ - result = callGraphQL(query) - return result["configuration"]["general"]["scrapersPath"] - - -def callGraphQL(query, variables=None): - # Session cookie for authentication - graphql_port = FRAGMENT_SERVER['Port'] - graphql_scheme = FRAGMENT_SERVER['Scheme'] - graphql_cookies = { - 'session': FRAGMENT_SERVER.get('SessionCookie').get('Value') - } - graphql_headers = { - "Accept-Encoding": "gzip, deflate, br", - "Content-Type": "application/json", - "Accept": "application/json", - "Connection": "keep-alive", - "DNT": "1" - } - if FRAGMENT_SERVER.get('Domain'): - graphql_domain = FRAGMENT_SERVER['Domain'] - else: - if FRAGMENT_SERVER.get('Host'): - graphql_domain = FRAGMENT_SERVER['Host'] - else: - graphql_domain = 'localhost' - # Because i don't understand how host work... - graphql_domain = 'localhost' - # Stash GraphQL endpoint - graphql_url = graphql_scheme + "://" + \ - graphql_domain + ":" + str(graphql_port) + "/graphql" - - json = {'query': query} - if variables is not None: - json['variables'] = variables - try: - response = requests.post( - graphql_url, json=json, headers=graphql_headers, cookies=graphql_cookies, timeout=10) - except: - sys.exit("[FATAL] Error with the graphql request, are you sure the GraphQL endpoint ({}) is correct.".format( - graphql_url)) - if response.status_code == 200: - result = response.json() - if result.get("error"): - for error in result["error"]["errors"]: - raise Exception("GraphQL error: {}".format(error)) - if result.get("data"): - return result.get("data") - elif response.status_code == 401: - sys.exit("HTTP Error 401, Unauthorised.") - else: - raise ConnectionError("GraphQL query failed:{} - {}. Query: {}. Variables: {}".format( - response.status_code, response.content, query, variables)) - - -def file_getlastline(path): - with open(path, 'r', encoding="utf-8") as f: - for line in f: - u_match = re.search(r"^\s*#\s*last updated", line.lower()) - if u_match: - return line.strip() - return None - - -def get_date(line): - try: - date = datetime.strptime(re.sub(r".*#.*Last Updated\s*", "", line), "%B %d, %Y") - except: - return None - return date - - -scraper_folder_path = graphql_getScraperPath() -GITHUB_LINK = "https://github.com/stashapp/CommunityScrapers/archive/refs/heads/master.zip" - -try: - r = requests.get(GITHUB_LINK, timeout=10) -except: - sys.exit("Failing to download the zip file.") -zip_path = os.path.join(scraper_folder_path, "github.zip") -log.LogDebug(zip_path) -with open(zip_path, "wb") as zip_file: - zip_file.write(r.content) - -with zipfile.ZipFile(zip_path) as z: - change_detected = False - - for filename in z.namelist(): - # Only care about the scrapers folders - if "/scrapers/" in filename and filename.endswith(".yml"): - # read the file - line = bytes() - # Filename abc.yml - gh_file = os.path.basename(filename) - - # Filename /scrapers//abc.yml - if filename.endswith(f"/scrapers/{gh_file}") == False: - log.LogDebug("Subdirectory detected: " + filename) - subdir = re.findall('\/scrapers\/(.*)\/.*\.yml', filename) - - if len(subdir) != 1: - log.LogError(f"Unexpected number of matching subdirectories found. Expected 1. Found {len(subdir)}.") - exit(1) - - gh_file = subdir[0] + "/" + gh_file - - log.LogDebug(gh_file) - path_local = os.path.join(scraper_folder_path, gh_file) - gh_line = None - yml_script = None - if OVERWRITE: - with z.open(filename) as f: - scraper_content = f.read() - with open(path_local, 'wb') as yml_file: - yml_file.write(scraper_content) - log.LogInfo("Replacing/Creating {}".format(gh_file)) - continue - with z.open(filename) as f: - for line in f: - script_match = re.search(r"action:\sscript", line.decode().lower()) - update_match = re.search(r"^\s*#\s*last updated", line.decode().lower()) - if script_match: - yml_script = True - if update_match: - gh_line = line.decode().strip() - break - # Got last line - if gh_line is None: - log.LogError("[Github] Line Error ({}) ".format(gh_file)) - continue - gh_date = get_date(gh_line) - if gh_date is None: - log.LogError("[Github] Date Error ({}) ".format(gh_file)) - continue - elif os.path.exists(path_local): - # Local Part - local_line = file_getlastline(path_local) - if local_line is None: - log.LogError("[Local] Line Error ({}) ".format(gh_file)) - continue - local_date = get_date(local_line.strip()) - if local_date is None: - log.LogError("[Local] Date Error ({}) ".format(gh_file)) - continue - if gh_date > local_date and CHECK_LOG: - change_detected = True - - if yml_script: - log.LogInfo("[{}] New version on github (Can be any of the related files)".format(gh_file)) - else: - log.LogInfo("[{}] New version on github".format(gh_file)) - elif GET_NEW_FILE: - change_detected = True - # File don't exist local so we take the github version. - with z.open(filename) as f: - scraper_content = f.read() - with open(path_local, 'wb') as yml_file: - yml_file.write(scraper_content) - log.LogInfo("Creating {}".format(gh_file)) - continue - elif CHECK_LOG and IGNORE_MISS_LOCAL == False: - change_detected = True - - log.LogWarning("[{}] File don't exist locally".format(gh_file)) - -if change_detected == False: - log.LogInfo("Scrapers appear to be in sync with GitHub version.") - -os.remove(zip_path) diff --git a/plugins/GHScraper_Checker/GHScraper_Checker.yml b/plugins/GHScraper_Checker/GHScraper_Checker.yml deleted file mode 100644 index 48188be4..00000000 --- a/plugins/GHScraper_Checker/GHScraper_Checker.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: GHScraper_Checker -description: Check the community scraper repo. -version: 0.1.1 -url: https://github.com/stashapp/CommunityScripts/tree/main/plugins/GHScraper_Checker -exec: - - python - - "{pluginDir}/GHScraper_Checker.py" -interface: raw -tasks: - - name: 'Status Check' - description: "Show in log if you don't have the scraper or a new version is available." - defaultArgs: - mode: CHECK - - name: 'Getting new files' - description: "Download scraper that don't exist in your scraper folder." - defaultArgs: - mode: NEWFILE -# - name: 'Overwrite everything' -# description: 'Replace your scraper by github version. Overwrite anything existing.' -# defaultArgs: -# mode: OVERWRITE diff --git a/plugins/GHScraper_Checker/log.py b/plugins/GHScraper_Checker/log.py deleted file mode 100644 index f3812522..00000000 --- a/plugins/GHScraper_Checker/log.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys - - -# Log messages sent from a plugin instance are transmitted via stderr and are -# encoded with a prefix consisting of special character SOH, then the log -# level (one of t, d, i, w, e, or p - corresponding to trace, debug, info, -# warning, error and progress levels respectively), then special character -# STX. -# -# The LogTrace, LogDebug, LogInfo, LogWarning, and LogError methods, and their equivalent -# formatted methods are intended for use by plugin instances to transmit log -# messages. The LogProgress method is also intended for sending progress data. -# - -def __prefix(level_char): - start_level_char = b'\x01' - end_level_char = b'\x02' - - ret = start_level_char + level_char + end_level_char - return ret.decode() - - -def __log(level_char, s): - if level_char == "": - return - - print(__prefix(level_char) + s + "\n", file=sys.stderr, flush=True) - - -def LogTrace(s): - __log(b't', s) - - -def LogDebug(s): - __log(b'd', s) - - -def LogInfo(s): - __log(b'i', s) - - -def LogWarning(s): - __log(b'w', s) - - -def LogError(s): - __log(b'e', s) - - -def LogProgress(p): - progress = min(max(0, p), 1) - __log(b'p', str(progress)) From 2ac5bd44b00207ebd42b39fb446e897b44a54edf Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 13:21:33 -0500 Subject: [PATCH 23/91] Add Themes --- themes/Theme-BlackHole/Theme-BlackHole.css | 91 + themes/Theme-BlackHole/Theme-BlackHole.yml | 6 + themes/Theme-ModernDark/Theme-ModernDark.css | 484 +++++ themes/Theme-ModernDark/Theme-ModernDark.yml | 6 + themes/Theme-NeonDark/Theme-NeonDark.css | 1081 ++++++++++ themes/Theme-NeonDark/Theme-NeonDark.yml | 6 + themes/Theme-Night/Theme-Night.css | 189 ++ themes/Theme-Night/Theme-Night.yml | 6 + themes/{plex => Theme-Plex}/README.md | 10 +- .../plex.css => Theme-Plex/Theme-Plex.css} | 0 themes/Theme-Plex/Theme-Plex.yml | 6 + themes/Theme-Pulsar/Theme-Pulsar.css | 1787 ++++++++++++++++ themes/Theme-Pulsar/Theme-Pulsar.yml | 6 + .../Theme-PulsarLight/Theme-PulsarLight.css | 1830 +++++++++++++++++ .../Theme-PulsarLight/Theme-PulsarLight.yml | 6 + 15 files changed, 5509 insertions(+), 5 deletions(-) create mode 100644 themes/Theme-BlackHole/Theme-BlackHole.css create mode 100644 themes/Theme-BlackHole/Theme-BlackHole.yml create mode 100644 themes/Theme-ModernDark/Theme-ModernDark.css create mode 100644 themes/Theme-ModernDark/Theme-ModernDark.yml create mode 100644 themes/Theme-NeonDark/Theme-NeonDark.css create mode 100644 themes/Theme-NeonDark/Theme-NeonDark.yml create mode 100644 themes/Theme-Night/Theme-Night.css create mode 100644 themes/Theme-Night/Theme-Night.yml rename themes/{plex => Theme-Plex}/README.md (80%) rename themes/{plex/plex.css => Theme-Plex/Theme-Plex.css} (100%) create mode 100644 themes/Theme-Plex/Theme-Plex.yml create mode 100644 themes/Theme-Pulsar/Theme-Pulsar.css create mode 100644 themes/Theme-Pulsar/Theme-Pulsar.yml create mode 100644 themes/Theme-PulsarLight/Theme-PulsarLight.css create mode 100644 themes/Theme-PulsarLight/Theme-PulsarLight.yml diff --git a/themes/Theme-BlackHole/Theme-BlackHole.css b/themes/Theme-BlackHole/Theme-BlackHole.css new file mode 100644 index 00000000..66ca41ab --- /dev/null +++ b/themes/Theme-BlackHole/Theme-BlackHole.css @@ -0,0 +1,91 @@ +/* Black Hole Theme by BViking78 v2.0 */ +/* STASH GENERAL */ +/* Set Background to Black & Optional Custom Image */ +body { + background: black url("") no-repeat fixed center; + background-size: contain; +} + +/* Change Top Nav Bar Colors */ +.bg-dark { + background-color: #000000!important; +} + +/* Set Red Border on Button on Hover */ +.btn-primary.btn:hover { + border: 1px solid red; +} + +/* Set Background to Transparent for Tags/Performers Popups*/ +.fade.hover-popover-content { + background: transparent; +} + +/* Zoom Performers image when Hover*/ +.hover-popover-content { + max-width: initial; +} +.image-thumbnail:hover { + height: 100%; +} + +/* Set Opacity Studio Logo to 100% */ +.scene-studio-overlay { + opacity: 100%; +} + +/* Making Checkbox Slightly Bigger */ +.grid-card .card-check { + top: 0.9rem; + width: 1.75rem; +} + +/* Center Titles on Cards */ +.grid-card a .card-section-title { + text-align: center; +} + +/* Setting Background on Cards to Black and Borders to "Stash Grey" */ +.card { + background-color: black; + border: 1px solid #30404d; +} + +/* STASH MAIN PAGE*/ +/* Change Card Header Color */ +.card-header { + background: black; + border: 1px solid white; +} +/* Change Markdown Color */ +.card-body { + background: black; + border: 1px solid white; +} + +/* SCENE PAGE */ +/* Hide the scene scrubber */ +.scrubber-wrapper { + display: none; +} + +/* Setting Row "Scrape With" Background to Black */ +#scene-edit-details .edit-buttons-container { + background-color: black; +} + +/* Setting Other Rows Background to Black */ +div.react-select__control { + background-color: black; +} + +/* SETTING */ +/* Setting Text Input Border to White */ +.input-control, .text-input { + border: 1px solid white; +} + +/* Setting Background on Task Queue to Black */ +#tasks-panel .tasks-panel-queue { + background-color: black; +} diff --git a/themes/Theme-BlackHole/Theme-BlackHole.yml b/themes/Theme-BlackHole/Theme-BlackHole.yml new file mode 100644 index 00000000..a5ded900 --- /dev/null +++ b/themes/Theme-BlackHole/Theme-BlackHole.yml @@ -0,0 +1,6 @@ +name: Theme - BlackHole +description: BlackHole Theme for Stash by BViking78 +version: 2.0.0 +ui: + css: + - Theme-BlackHole.css diff --git a/themes/Theme-ModernDark/Theme-ModernDark.css b/themes/Theme-ModernDark/Theme-ModernDark.css new file mode 100644 index 00000000..c2a23f90 --- /dev/null +++ b/themes/Theme-ModernDark/Theme-ModernDark.css @@ -0,0 +1,484 @@ +/* Modern Dark Theme by cj13 v1.2 */ + :root { + --nav-color: #212121; + --body-color: #191919; + --card-color: #2a2a2a; + --plex-yelow: #e5a00d; + --tag-color: #555555; +} + #scrubber-position-indicator { + background-color: #e5a00d; +} + .scrubber-tags-background { + background-color: #e5a00d; + opacity: 30%; +} + body { + width: 100%; + height: 100%; + background: var(--body-color); +} + .text-white { + color: #cbced2 !important; +} + #root { + position: absolute; + width: 100%; + height: 100%; + z-index: 2; +} + div.react-select__menu, div.dropdown-menu { + background-color: var(--card-color); +} + * { + scrollbar-color: hsla(0, 0%, 100%, .2) transparent; +} + .bg-dark { + background-color: var(--nav-color)!important; +} + .card { + background-color: #30404d; + border-radius: 3px; + box-shadow: 0 0 0 1px rgba(16, 22, 26, .4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); + padding: 20px; + background-color: var(--card-color); +} + .text-input, .text-input:focus, .text-input[readonly], .text-input:disabled { + background-color: var(--card-color); +} + #scrubber-forward { + background-color: transparent; + border: 1px solid #555; +} + .scrubber-button { + background-color: transparent; + border: 1px solid #555; +} + .bg-secondary { + background-color: var(--card-color) !important; +} + .text-white { + color: #eee !important; +} + .border-secondary { + border-color: #2f3335 !important; +} + .btn-secondary.filter-item.col-1.d-none.d-sm-inline.form-control { + background-color: rgba(0, 0, 0, .15); +} + .btn-secondary { + color: #eee; + background-color: rgba(0, 0, 0, .15); + border-color: var(--tag-color); +} + .pagination .btn:last-child { + border-right: 1px solid var(--tag-color); +} + .pagination .btn:first-child { + border-left: 1px solid var(--tag-color); +} + a { + color: hsla(0, 0%, 100%, .45); +} + .btn.active { + background-color: #2f3335; + color: #f5f8fa; +} + minimal.w-100.active.btn.btn-primary { + background-color: #2f3335; + color: #f5f8fa; +} + .btn-primary { + color: #fff; + background-color: #cc7b19; + border-color: #cc7b19; + font-weight: bold; +} + .btn-primary:hover { + color: #fff; + background-color: #e59029; + border-color: #e59029 font-weight: bold; +} + .nav-tabs .nav-link.active { + color: #eee; +} + .nav-tabs .nav-link.active:hover { + border-bottom-color: #eee; + outline: 0; +} + .nav-tabs .nav-link { + color: hsla(0,0%,100%,.65); +} + .tag-item { + background-color: var(--tag-color); + color: #fff; +} + .input-control, .input-control:focus { + background-color: rgba(16, 22, 26, .3); +} + #performer-page .image-container .performer { + background-color: rgba(0, 0, 0, .45); + box-shadow: 0 0 2px rgba(0, 0, 0, .35); +} + .btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled):active, .show>.btn-primary.dropdown-toggle { + color: #fff; + border-color: #eee; +} + .nav-pills .nav-link.active, .nav-pills .show>.nav-link { + background-color: var(--nav-color); +} + .btn-primary.focus, .btn-primary:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .btn-primary:not(:disabled):not(.disabled):active:focus, .show>.btn-primary.dropdown-toggle:focus { + box-shadow: none; +} + .btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled):active, .show>.btn-primary.dropdown-toggle { + color: #fff; + background-color: #2f3335; + border-color: #eee; +} + input[type="range"]::-moz-range-track { + background: hsla(0, 0%, 100%, .25); +} + input[type="range"]::-moz-range-thumb { + background: #bcbcbc; +} + div.react-select__control { + background-color: hsla(0, 0%, 39.2%, .4); + color: #182026; + border-color: var(--card-color); + cursor: pointer; +} + .scene-wall-item-text-container { + background: radial-gradient(farthest-corner at 50% 50%, rgba(50, 50, 50, .5) 50%, #323232 100%); + color: #eee; +} + .filter-container, .operation-container { + background-color: rgba(0, 0, 0, .15); + box-shadow: none; + margin-top: -10px; + padding: 10px; +} + .container-fluid, .container-lg, .container-md, .container-sm, .container-xl { + width: 100%; + margin-right: 0px; + margin-left: 0px; +} + .btn-link { + font-weight: 500; + color: #eee; + text-decoration: none; +} + button.minimal.brand-link.d-none.d-md-inline-block.btn.btn-primary { + text-transform: uppercase; + font-weight: bold; +} + a:hover { + color: hsla(0, 0%, 100%, .7); +} + option { + background-color: var(--nav-color); +} + .folder-list .btn-link { + color: #2c2e30; +} + #performer-scraper-popover { + z-index: 10; +} + .filter-container, .operation-container { + background-color: transparent; +} + .search-item { + background-color: var(--card-color); +} + .selected-result { + background-color: var(--card-color); +} + .selected-result:hover { + background-color: var(--card-color); +} + .performer-select-active .react-select__control, .studio-select-active .react-select__control { + background-color: hsla(0, 0%, 39.2%, .4); +} + #scene-edit-details .edit-buttons-container { + margin: 0px; + background: var(--body-color); +} + #tasks-panel .tasks-panel-queue { + background: var(--body-color); +} + .job-table.card { + background-color: var(--card-color); +} + .modal-header, .modal-body, .modal-footer { + background-color: #2d3744; + background-repeat: no-repeat; + background-size: cover; + background-attachment: fixed; + /* background-position: center; + */ +} + .folder-list .btn-link { + color: #fff; +} + .modal-header, .modal-body, .modal-footer { + background-color: #30404d; + color: #f5f8fa; + background-repeat: no-repeat; + background-size: cover; + background-attachment: fixed; + background-position: center; +} + @media (max-width: 575.98px) and (orientation: portrait) { + .scene-card-preview-image { + height: calc(100vw * (9 / 16)); + } + .gallery-tabs .mr-auto.nav.nav-tabs { + display: grid; + grid-auto-flow: column; + text-align: center; + left: 0; + right: 0; + position: fixed; + } + .VideoPlayer.portrait .video-js { + height: 56.25vw; + } + .gallery-container .tab-content { + top: 3rem; + position: fixed; + height: calc(100vh - 6.5rem); + } + .gallery-tabs { + display: none; + } + .btn-toolbar { + padding-top: 1rem; + } + body { + padding: 0rem 0 5rem; + } + .scene-tabs .mr-auto.nav.nav-tabs { + background-color: #121212; + display: grid; + grid-auto-flow: column; + height: 3rem; + left: 0; + margin: 0; + margin-bottom: 0; + max-height: 3rem; + padding-bottom: 2.2rem; + padding-top: 0.1rem; + position: fixed; + right: 0; + text-align: center; + top: calc(100vw * (9 / 16)); + white-space: nowrap; + z-index: 20; + } + .scene-tabs.order-xl-first.order-last { + height: calc(100vh - (100vw * (9 / 16) + 8.5rem)); + top: calc((100vw * (9 / 16)) + 5rem); + position: fixed; + } + .tab-content { + overflow-y: auto; + overflow-x: hidden; + } + .studio-card { + width: 100%; + height: 294px; + } + .movie-card { + width: 45%; + } + .performer-card-image { + height: 19rem; + } + .performer-card { + width: 14rem; + height: 27.5rem; + } + .scene-performers .performer-card-image { + height: 19rem; + } + .scene-performers .performer-card { + width: 14rem; + height: 27.5rem; + } + .movie-card .TruncatedText { + display: none; + } + .nav-tabs .nav-link.active:hover { + outline: 0; + border-bottom: 2px solid + } + #performer-details-tab-edit{ + display: none; + } + #performer-details-tab-operations{ + display: none; + } + .scene-tabs .ml-auto.btn-group { + position: fixed; + right: 1rem; + top: calc((100vw * (9 / 16)) + 2.7rem); + } + .stats-element { + flex-grow: 1; + margin: auto 0rem; + } + .stats { + margin-left: 0px; + } + .top-nav { + bottom: 0; + top: auto; + } + div[data-rb-event-key="/images"] { + display: none; + } + div[data-rb-event-key="/scenes/markers"] { + display: none; + } + .row.justify-content-center.scene-performers { + max-height: 450px; + display: flex; + flex-direction: column; + overflow: auto; + border-top: solid 2px #2d3035; + border-bottom: solid 2px #2d3035; + padding-top: .5rem; + padding-bottom: .5rem; + } + .scene-tabs { + max-height: 100%; + } +} + dd { + word-break: break-word; +} + .btn-secondary { + color: hsla(0,0%,100%,.65); +} + .btn-secondary:hover { + color: white; + z-index: 0; + border-color: var(--nav-color); + background-color: rgba(0, 0, 0, .15); +} + .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show>.btn-secondary.dropdown-toggle { + background-color: rgba(0, 0, 0, .35); +} + .btn-secondary:focus, .btn-secondary.focus { + background-color: rgba(0, 0, 0, .35); +} + .scrubber-wrapper { + background-color: rgba(0, 0, 0, .3); + border-style: ridge; + border-color: #555555; +} + a.minimal:hover:not(:disabled), button.minimal:hover:not(:disabled) { + background: none; + color: white; +} + .btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled):active, .show>.btn-primary.dropdown-toggle { + color: var(--plex-yelow); + border-bottom: solid; + background: none; +} + a.minimal, button.minimal { + color: hsla(0,0%,100%,.65); +} + .nav-pills .nav-link.active, .nav-pills .show>.nav-link { + background: none; + border-left: solid; + color: var(--plex-yelow); +} + .nav-link { + color: hsla(0,0%,100%,.65); +} + .nav-link:hover, .nav-link:focus { + color: white; + background-color: hsla(0,0%,100%,.08); +} + .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { + background-color: transparent; +} + .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show>.btn-secondary.dropdown-toggle { + background: none; + color: var(--plex-yelow); + border-bottom: solid; +} + .nav-tabs .nav-link.active { + color: var(--plex-yelow); + background: none; +} + .nav-tabs .nav-link:hover { + border-bottom: none; +} + .custom-control-input:checked~.custom-control-label:before { + color: #fff; + border-color: var(--plex-yelow); + background-color: var(--plex-yelow); +} + .custom-switch .custom-control-input:disabled:checked~.custom-control-label:before { + background-color: var(--plex-yelow); +} + .btn-primary.disabled, .btn-primary:disabled { + color: #fff; + background-color: #e59029; + border-color: #e59029; + font-weight: bold; +} + .btn-primary:focus, .btn-primary.focus { + background-color: #cc7b19; + border-color: #cc7b19; + font-weight: bold; +} + .btn-danger { + color: hsla(0,0%,100%,.75); + background-color: #b32; + border-color: #b32; + font-weight: bold; +} + .btn-danger:hover { + color: white; + background-color: #b32; + border-color: #b32; +} + .brand-link { + background-color: var(--nav-color)!important; +} + .hover-popover-content { + max-width: 32rem; + text-align: center; + background: var(--nav-color); +} + .progress-bar { + background-color: var(--plex-yelow); +} + .modal-header, .modal-body, .modal-footer { + background-color: var(--body-color); +} + .btn-secondary.disabled, .btn-secondary:disabled { + background-color: var(--card-color); + border-color: var(--card-color); + border-left: none!important; + border-right: none!important; +} + #queue-viewer .current { + background-color: var(--card-color); +} + .tab-content .card-popovers .btn { + padding-left: .4rem; + padding-right: .4rem; +} +/* .gallery-tabs, .scene-tabs, .scene-player-container { + background-color: var(--nav-color); +} + */ + .react-select__menu-portal { + z-index: 2; +} + .video-js .vjs-play-progress { + background-color: #e5a00d; +} diff --git a/themes/Theme-ModernDark/Theme-ModernDark.yml b/themes/Theme-ModernDark/Theme-ModernDark.yml new file mode 100644 index 00000000..6e3c40ba --- /dev/null +++ b/themes/Theme-ModernDark/Theme-ModernDark.yml @@ -0,0 +1,6 @@ +name: Theme - ModernDark +description: ModernDark Theme for Stash by cj13 +version: 1.2 +ui: + css: + - Theme-ModernDark.css diff --git a/themes/Theme-NeonDark/Theme-NeonDark.css b/themes/Theme-NeonDark/Theme-NeonDark.css new file mode 100644 index 00000000..0126434a --- /dev/null +++ b/themes/Theme-NeonDark/Theme-NeonDark.css @@ -0,0 +1,1081 @@ +/* Neon Dark Theme by Dankonite */ +:root +{ + --background-color:#101010; + --blue-accent:#137cbd; + --card-radius:.75rem; + --disabled-color:#181818; + --font-color:#fff; + --lighter-gray:#303030; + --menu-gray:#202020; + --menu-rounding:1rem; + --not-card-radius:.25rem; + --red-delete:#DB3737 +} + +.btn +{ + border-radius:var(--not-card-radius) +} + +.btn-primary:not(:disabled):not(.disabled):not(.brand-link):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle +{ + background-color:var(--lighter-gray); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.dropdown-item +{ + border-radius:var(--menu-rounding) +} + +.edit-buttons-container>* +{ + margin-bottom:1rem +} + +.fa-chevron-up,.fa-chevron-down +{ + color:var(--blue-accent) +} + +.form-control +{ + border-radius:var(--not-card-radius); + padding-right:1.3rem +} + +.input-group-text +{ + background-color:var(--menu-gray); + border:1px solid var(--blue-accent); + color:var(--font-color) +} + +.input-group.has-validation>.input-group-append>div>div>button +{ + border-bottom-left-radius:0!important; + border-top-left-radius:0!important +} + +.nav-pills .nav-link +{ + border-radius:var(--not-card-radius) +} + +.row +{ + margin-left:0; + margin-right:0 +} + +.saved-filter-list-menu>div>div +{ + margin-bottom:1rem +} + +.search-item +{ + background-color:var(--menu-gray); + border-radius:.25rem; + padding:1rem +} + +.set-as-default-button +{ + margin-top:1rem +} + +.setting-section .setting:not(:last-child) +{ + border-bottom:1px solid var(--blue-accent) +} + +a.bg-secondary:hover,a.bg-secondary:focus,button.bg-secondary:hover,button.bg-secondary:focus +{ + background-color:var(--lighter-gray)!important +} + +button.brand-link +{ + font-size:0; + visibility:hidden!important +} + +button.brand-link:after +{ + align-items:center; + border:1px solid var(--blue-accent)!important; + border-radius:var(--not-card-radius); + content:"Home"; + display:inline-block; + font-size:1rem; + height:40px; + padding:7px 12px; + position:relative; + top:-1px; + vertical-align:middle; + visibility:visible +} + +button.brand-link:hover:after +{ + background-color:var(--lighter-gray) +} + +div.row>h4 +{ + margin:0; + padding:1rem +} + +div.row>hr.w-100 +{ + padding-bottom:1rem +} + +input.bg-secondary.text-white.border-secondary.form-control,.date-input.form-control,.text-input.form-control +{ + height:33.5px +} + +@media (max-width: 1199.98px) { + .brand-link:after + { + margin-left:-16px + } + + .top-nav .btn + { + padding:0 12px + } +} + +@media (min-width: 1199.98px) { + .navbar-collapse>.navbar-nav>div>a + { + height:40px + } +} + +@media (min-width: 576px) { +#settings-menu-container +{ + padding:1rem; + position:fixed +} +} + +.bs-popover-bottom>.arrow:after,.bs-popover-auto[x-placement^=bottom]>.arrow:after +{ + border-bottom-color:var(--blue-accent); + border-width:0 .5rem .5rem; + top:1px +} + +.btn-toolbar>.btn-group>.dropdown>button,.query-text-field,.form-control,.dropdown,.dropdown-toggle +{ + height:100% +} + +.navbar-brand +{ + display:inline-block; + font-size:1.25rem; + line-height:inherit; + margin-right:0; + padding-bottom:.3125rem; + padding-top:.3125rem; + white-space:nowrap +} + +.navbar-collapse>.navbar-nav>div>a +{ + border:1px solid var(--blue-accent) +} + +.navbar-expand-xl .navbar-nav .nav-link +{ + padding-left:.5rem; + padding-right:0 +} + +.setting-section .setting-group:not(:last-child) +{ + border-bottom:1px solid var(--blue-accent) +} + +a +{ + color:var(--font-color) +} + +h6.col-md-2.col-4 +{ + display:flex; + justify-content:center +} + +img +{ + border-bottom:1px solid var(--blue-accent)!important +} + +@media(min-width: 576px) { + .offset-sm-3 + { + border-left:1px solid var(--blue-accent) + } +} + +.TruncatedText.scene-card__description +{ + font-size:.9rem +} + +.brand-link +{ + padding:0 +} + +.btn-primary +{ + background-color:var(--menu-gray); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.btn-secondary +{ + background-color:var(--menu-gray); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.btn-secondary.disabled,.btn-secondary:disabled +{ + background-color:var(--disabled-color); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.btn-secondary:focus,.btn-secondary.focus +{ + background-color:var(--lighter-gray); + border-color:var(--blue-accent); + box-shadow:0 0 .3rem .3rem var(--blue-accent); + color:var(--font-color) +} + +.btn-secondary:hover +{ + background-color:var(--lighter-gray); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle +{ + background-color:var(--lighter-gray); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.btn-secondary:not(:disabled):not(.disabled):active:focus,.btn-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-secondary.dropdown-toggle:focus +{ + box-shadow:var(--blue-accent) +} + +.btn-toolbar +{ + justify-content:flex-start; + padding-top:.5rem +} + +.btn:focus,.btn.focus +{ + box-shadow:var(--blue-accent)!important +} + +.dropdown-item.disabled,.dropdown-item:disabled +{ + color:#adb5bd +} + +.dropdown-menu.show +{ + display:block; + padding:1rem +} + +.form-control::placeholder +{ + color:var(--font-color) +} + +.form-control:focus +{ + box-shadow:var(--blue-accent) +} + +.nav-menu-toggle +{ + border:1px solid var(--blue-accent)!important +} + +.query-text-field:active,.query-text-field:focus +{ + box-shadow:0 0 .3rem .3rem var(--blue-accent)!important +} + +.scene-card a,.gallery-card a +{ + color:var(--font-color) +} + +body +{ + background-color:var(--background-color); + color:var(--font-color); + text-align:left +} + +div.navbar-buttons>:not(.mr-2) +{ + border:1px solid var(--blue-accent)!important; + border-radius:var(--not-card-radius) +} + +h5,.h5 +{ + font-size:1.1rem +} + +hr +{ + border-top:1px solid var(--blue-accent) +} + +@media (min-width:576px) { + .gallery-card + { + width:unset!important + } + + .performer-card + { + width:unset!important + } + + .tag-card-image + { + height:180px + } +} + +@media (max-width:576px) { + .gallery-card + { + width:unset!important + } + + .performer-card + { + width:unset!important + } + + .tag-card-image + { + height:120px + } +} + +#scrubber-current-position +{ + background-color:var(--blue-accent); + height:30px; + left:50%; + position:absolute; + width:2px; + z-index:1 +} + +#scrubber-position-indicator +{ + background-color:var(--lighter-gray); + border-right:2px solid var(--blue-accent); + height:20px; + left:-100%; + position:absolute; + width:100%; + z-index:0 +} + +.badge-info +{ + background-color:var(--menu-gray); + border:1px solid var(--blue-accent); + color:var(--font-color) +} + +.badge-secondary +{ + background-color:var(--lighter-gray); + border:1px solid var(--blue-accent); + border-radius:.25rem; + color:var(--font-color) +} + +.bg-dark +{ + background-color:var(--menu-gray)!important +} + +.bg-secondary +{ + background-color:var(--menu-gray)!important +} + +.border-secondary +{ + border-color:var(--blue-accent)!important +} + +.brand-link +{ + border:1px solid var(--blue-accent)!important +} + +.card +{ + background-color:var(--menu-gray); + border-radius:var(--card-radius)!important; + box-shadow:var(--blue-accent) +} + +.card +{ + border:1px solid var(--blue-accent) +} + +.filter-button .badge +{ + border-radius:999px; + right:-7px; + top:-6px; + z-index:3!important +} + +.gallery-card +{ + height:min-content!important +} + +.gallery-card.card +{ + padding-bottom:0 +} + +.modal-footer +{ + border-radius:1rem; + border-top:1px solid var(--blue-accent); + border-top-left-radius:0; + border-top-right-radius:0 +} + +.modal-header +{ + border-bottom:1px solid var(--blue-accent); + border-bottom-left-radius:0!important; + border-bottom-right-radius:0!important; + border-radius:1rem +} + +.modal-header,.modal-body,.modal-footer +{ + background-color:var(--menu-gray); + color:var(--font-color) +} + +.nav-pills .nav-link.active,.nav-pills .show>.nav-link +{ + background-color:var(--menu-gray); + border:1px solid var(--blue-accent); + color:var(--font-color) +} + +.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link +{ + background-color:var(--menu-gray); + border-color:var(--blue-accent); + color:var(--font-color) +} + +.nav-tabs .nav-link:hover +{ + border-bottom:2px solid var(--blue-accent) +} + +.pagination .btn +{ + border-left:1px solid var(--blue-accent); + border-right:1px solid var(--blue-accent) +} + +.pagination .btn:first-child +{ + border-left:1px solid var(--blue-accent) +} + +.pagination .btn:last-child +{ + border-right:1px solid var(--blue-accent) +} + +.performer-card .fi +{ + bottom:.3rem; + filter:drop-shadow(0 0 2px rgba(0,0,0,.9)); + height:2rem; + position:absolute; + right:.2rem; + width:3rem +} + +.popover +{ + background-color:var(--menu-gray); + border:1px solid var(--blue-accent)!important; + border-radius:var(--card-radius) +} + +.popover-header +{ + background-color:var(--lighter-gray); + border-bottom:1px solid var(--blue-accent) +} + +.query-text-field +{ + border:1px solid var(--blue-accent)!important +} + +.scene-header>h3>.TruncatedText +{ + text-align:left +} + +.scene-specs-overlay,.scene-interactive-speed-overlay,.scene-studio-overlay,span.scene-card__date +{ + font-weight:900!important +} + +.scrubber-tags-background +{ + background-color:var(--menu-gray); + height:20px; + left:0; + position:absolute; + right:0 +} + +::selection +{ + background-color:var(--lighter-gray)!important; + color:var(--font-color)!important +} + +a.minimal,button.minimal +{ + color:var(--font-color) +} + +body ::-webkit-scrollbar-thumb +{ + background:var(--blue-accent) +} + +body ::-webkit-scrollbar-thumb:hover +{ + background:var(--blue-accent) +} + +body ::-webkit-scrollbar-thumb:window-inactive +{ + background:var(--blue-accent) +} + +body ::-webkit-scrollbar-track +{ + background:var(--menu-gray) +} + +hr +{ + margin:0 +} + +@media(orientation:portrait) { + .performer-card-image + { + height:25vh + } +} + +.card.performer-card +{ + padding:0 +} + +.fa-mars +{ + display:none +} + +.fa-transgender-alt +{ + display:none +} + +.fa-venus +{ + display:none +} + +.performer-card__age +{ + color:var(--font-color); + text-align:center +} + +.slick-list .gallery-card +{ + width:min-content +} + +.slick-slide .card +{ + height:min-content +} + +.slick-track +{ + margin-bottom:1rem; + top:0 +} + +.tag-sub-tags,.studio-child-studios +{ + display:none +} + +@media (max-width: 576px) { + .slick-list .scene-card,.slick-list .studio-card + { + width:44vw!important + } +} + +.card-popovers +{ + justify-content:space-around; + margin-bottom:2px; + margin-top:2px +} + +.card-section +{ + height:100%; + padding:0 .2rem +} + +.scene-specs-overlay +{ + bottom:.2rem; + right:.2rem +} + +.scene-studio-overlay +{ + line-height:.8rem; + max-width:50%; + right:.2rem; + top:.2rem +} + +@media (min-width: 1200px),(max-width: 575px) { + .scene-performers .performer-card + { + width:47vw + } + + .scene-performers .performer-card-image + { + height:22.5rem + } +} + +#queue-viewer .current +{ + background-color:var(--menu-gray) +} + +#scene-edit-details .edit-buttons-container +{ + background-color:var(--background-color) +} + +.edit-buttons>button +{ + margin-left:1px +} + +.scene-card__details,.gallery-card__details +{ + margin-bottom:0!important +} + +.setting-section .setting>div:last-child +{ + display:flex; + justify-content:center; + text-align:right +} + +span.scene-card__date +{ + display:flex; + font-size:.8em; + justify-content:flex-end; + margin-right:.2rem +} + +@media (min-width: 576px) and (min-height: 600px) { + #tasks-panel .tasks-panel-queue + { + background-color:var(--background-color) + } +} + +.gender-icon +{ + display:none +} + +.job-table.card +{ + background-color:var(--menu-gray) +} + +.scraper-table tr:nth-child(2n) +{ + background-color:var(--lighter-gray) +} + +a.marker-count +{ + display:none +} + +a[target='_blank'] +{ + display:none +} + +button.collapse-button.btn-primary:not(:disabled):not(.disabled):hover,button.collapse-button.btn-primary:not(:disabled):not(.disabled):focus,button.collapse-button.btn-primary:not(:disabled):not(.disabled):active +{ + color:var(--font-color) +} + +@media(max-width: 576px) { + .grid-card + { + width:47vw + } +} + +.TruncatedText +{ + text-align:center; + white-space:pre-line; + word-break:break-word +} + +.gallery-card +{ + width:min-content!important +} + +.gallery-card-image +{ + max-height:240px!important; + width:auto!important +} + +.grid-card .progress-bar +{ + background-color:var(--lighter-gray); + bottom:0 +} + +.grid-card a .card-section-title +{ + color:var(--font-color); + display:flex; + justify-content:center +} + +.ml-2.mb-2.d-none.d-sm-inline-flex +{ + display:none!important +} + +.tag-card +{ + padding:0; + width:auto!important +} + +body +{ + color:var(--font-color); + padding:3.6rem 0 0 +} + +div.mb-2 +{ + height:auto +} + +@media (max-width: 575.98px) and (orientation: portrait) { + body + { + padding:0 + } +} + +@media (min-width: 768px) { + .offset-md-3 + { + border-left:1px solid var(--blue-accent) + } +} + +.card +{ + background-color:var(--menu-gray); + padding:0 +} + +.container,.container-fluid,.container-xl,.container-lg,.container-md,.container-sm +{ + padding-left:0; + padding-right:0 +} + +.d-flex.justify-content-center.mb-2.wrap-tags.filter-tags +{ + margin:0!important +} + +.input-control,.input-control:focus,.input-control:disabled +{ + background-color:var(--lighter-gray) +} + +.input-control,.text-input +{ + border:1px solid var(--blue-accent); + color:var(--font-color) +} + +.navbar-buttons>.mr-2,.card hr +{ + margin:0!important +} + +.preview-button .fa-icon +{ + color:var(--font-color) +} + +.rating-banner +{ + display:none!important +} + +.scene-card-preview,.gallery-card-image,.tag-card-image,.image-card-preview +{ + width:100% +} + +.slick-dots li button:before +{ + content:"." +} + +.slick-dots li.slick-active button:before +{ + color:var(--blue-accent); + opacity:.75 +} + +.tag-item +{ + background-color:var(--lighter-gray); + border:1px solid var(--blue-accent); + color:var(--font-color) +} + +.tag-item .btn +{ + color:var(--font-color) +} + +.text-input,.text-input:focus,.text-input[readonly],.text-input:disabled +{ + background-color:var(--lighter-gray) +} + +.top-nav +{ + border-bottom:1px solid var(--blue-accent); + padding:0 2rem!important +} + +a.nav-utility,button[title='Help'],.nav-menu-toggle +{ + margin-left:.5rem +} + +button.brand-link,.top-nav .navbar-buttons .btn +{ + height:40px +} + +div.react-select__control +{ + background-color:var(--lighter-gray); + border-color:var(--blue-accent)!important; + color:var(--font-color) +} + +div.react-select__control .react-select__multi-value,div.react-select__multi-value__label,div.react-select__multi-value__remove +{ + background-color:var(--menu-gray); + color:var(--font-color)!important +} + +div.react-select__menu,div.dropdown-menu +{ + background-color:var(--menu-gray); + border:1px solid var(--blue-accent); + color:var(--font-color) +} + +div.react-select__multi-value +{ + border:1px solid var(--blue-accent); + border-radius:999px +} + +div.react-select__multi-value__label +{ + border-bottom-left-radius:999px; + border-top-left-radius:999px; + padding-right:8px +} + +div.react-select__multi-value__remove +{ + border-bottom-right-radius:999px; + border-top-right-radius:999px; + padding-left:0 +} + +div.react-select__multi-value__remove:hover +{ + background-color:var(--red-delete) +} + +div.react-select__placeholder +{ + color:var(--font-color) +} + +div[id='settings-menu-container'] +{ + padding-top:.5rem +} + +div[role='group'].ml-auto.btn-group>div +{ + margin-right:.5rem; + margin-top:.5rem +} + +@media (max-width: 575.98px) and (orientation: portrait) { + .container,.container-fluid,.container-xl,.container-lg,.container-nd,.container-sm + { + padding-top:3.5rem!important + } + + .top-nav + { + bottom:unset; + top:auto + } +} + +.grid-card a .card-section-title +{ + display:flex; + justify-content:center +} + +.markdown blockquote,.markdown pre +{ + background-color:var(--lighter-gray) +} + +.markdown code +{ + background-color:transparent; + color:var(--font-color) +} + +.markdown table tr:nth-child(2n) +{ + background-color:var(--lighter-gray) +} + +.details-list>*:nth-child(4n), .details-list>*:nth-child(4n - 1) { + background-color: var(--menu-gray); +} +dl.details-list { + grid-column-gap:0; +} +dt { + padding-right: .5rem; +} +dd { + margin-bottom: 0; + padding-left: .5rem; + border-left: 1px solid var(--blue-accent); +} +#performer-page .performer-image-container .performer { + border-radius:var(--menu-rounding); + border: 0!important; +} +.recommendations-container-edit .recommendation-row { + background-color: var(--menu-gray); + border-radius: 1rem; + border:1px solid var(--blue-accent); + margin-bottom: 10px; +} +.recommendations-container-edit.recommendations-container>div>div:first-of-type { + margin-top: calc(1rem + 10px); +} + diff --git a/themes/Theme-NeonDark/Theme-NeonDark.yml b/themes/Theme-NeonDark/Theme-NeonDark.yml new file mode 100644 index 00000000..623f5e8a --- /dev/null +++ b/themes/Theme-NeonDark/Theme-NeonDark.yml @@ -0,0 +1,6 @@ +name: Theme - NeonDark +description: NeonDark Theme for Stash by Dankonite +version: 1.0 +ui: + css: + - Theme-NeonDark.css diff --git a/themes/Theme-Night/Theme-Night.css b/themes/Theme-Night/Theme-Night.css new file mode 100644 index 00000000..c6777617 --- /dev/null +++ b/themes/Theme-Night/Theme-Night.css @@ -0,0 +1,189 @@ +/* +Night theme Version 0.1. +*/ + +body { + color: #bb0009; + background-color: #000000; + +} + +.bg-dark { + background-color: #1a1a1b!important; +} + +.card { + background-color: #30404d; + border-radius: 3px; + box-shadow: 0 0 0 1px rgba(16, 22, 26, .4), 0 0 0 rgba(16, 22, 26, 0), 0 0 0 rgba(16, 22, 26, 0); + padding: 20px; + background-color: rgba(0, 0, 0, .3); +} + +.bg-secondary { + background-color: #313437 !important; +} + +.text-white { + color: #bb0009 !important; +} + +.border-secondary { + border-color: #2f3335 !important; +} + +.btn-secondary.filter-item.col-1.d-none.d-sm-inline.form-control { + background-color: rgba(0, 0, 0, .15); +} + +.btn-secondary { + color: #bb0009; + background-color: rgba(0, 0, 0, .15); +} + +.btn-primary { + color: #bb0009; + background-color: #bb0009; +} + +a { + color: hsla(0, 0%, 100%, .45); +} + +.btn.active { + background-color: #2f3335; + color: #bb0009; +} + +minimal.w-100.active.btn.btn-primary { + background-color: #bb0009; + color: #bb0009; +} + +.btn-primary { + color: #fff; + background-color: #1a1a1b; + border-color: #374242; +} + +.nav-tabs .nav-link.active { + color: #bb0009; +} + +.nav-tabs .nav-link.active:hover { + border-bottom-color: #bb0009; + outline: 0; +} + +.btn-primary.btn.donate.minimal { + display: none; +} + +.btn-primary.btn.help-button.minimal { + display: none; +} + +.changelog { + display: none; +} + +.nav-tabs .nav-link { + outline: 0; + color #bb0009; +} + +.input-control, +.input-control:focus { + background-color: rgba(16, 22, 26, .3); +} + +#performer-page .image-container .performer { + background-color: rgba(0, 0, 0, .45); + box-shadow: 0 0 2px rgba(0, 0, 0, .35); +} + +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + border-color: #bb0009; +} + +.nav-pills .nav-link.active, +.nav-pills .show>.nav-link { + background-color: #1a1a1b; +} + + + +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + background-color: #2f3335; + border-color: #bb0009; +} + +input[type="range"]::-moz-range-track { + background: hsla(0, 0%, 100%, .25); +} + +input[type="range"]::-moz-range-thumb { + background: #bcbcbc; +} + +div.react-select__control { + background-color: hsla(0, 0%, 39.2%, .4); + color: #182026; + border-color: #394b59; + cursor: pointer; +} + +.scene-wall-item-text-container { + background: radial-gradient(farthest-corner at 50% 50%, rgba(50, 50, 50, .5) 50%, #323232 100%); + color: #bb0009; +} + +.btn-link { + font-weight: 500; + color: #bb0009; + text-decoration: none; +} + +button.minimal.brand-link.d-none.d-md-inline-block.btn.btn-primary { + text-transform: uppercase; + font-weight: bold; + color: #bb0009 +} + +a.minimal { + text-transform: uppercase; + font-weight: bold; + color: #bb0009 +} +a:hover { + color: hsla(0, 0%, 100%, .7); +} + +option { + background-color: #1a1a1b; +} +.scrubber-tags-background { + background-color: #202351; +} + +.btn-primary.btn.settings-button.minimal { + color: #007dd0; +} + +button.minimal.btn.btn-primary { + color: #007dd0; +} + +.btn-primary.btn.logout-button.minimal { + color: #00bb2f; +} + +.scrubber-viewport { + background-color: #1f062d; +} diff --git a/themes/Theme-Night/Theme-Night.yml b/themes/Theme-Night/Theme-Night.yml new file mode 100644 index 00000000..4c7e39f4 --- /dev/null +++ b/themes/Theme-Night/Theme-Night.yml @@ -0,0 +1,6 @@ +name: Theme - Night +description: Night Theme for Stash (unknown author) +version: 0.1 +ui: + css: + - Theme-Night.css diff --git a/themes/plex/README.md b/themes/Theme-Plex/README.md similarity index 80% rename from themes/plex/README.md rename to themes/Theme-Plex/README.md index 64c6bb24..386bbbbe 100644 --- a/themes/plex/README.md +++ b/themes/Theme-Plex/README.md @@ -10,11 +10,11 @@ The Plex Theme will only change the look and feel of the Stash interface. It wil ## Install -1. Open User Interface Configuration panel in settings. (http://localhost:9999/settings?tab=interface) +1. Open User Interface Configuration panel in settings. (http://localhost:9999/settings?tab=plugins) -2. Tick/Enable Custom CSS ✅ +2. Find the Theme in the listing, in the default Community repo -3. Copy & Paste [CSS Code](https://github.com/stashapp/CommunityScripts/blob/main/themes/plex/plex.css) to the Custom CSS text area. +3. Click Install ### Optional - Host Backgrounds Locally @@ -24,6 +24,6 @@ _These steps are optional, by default this theme uses the Github hosted image li 2. Place `background.png` and `noise.png` in `~/.stash` on macOS / Linux or `C:\Users\YourUsername\.stash` on Windows. Then edit the `background-image: url("")` attributes like below: -The [body](https://github.com/stashapp/CommunityScripts/blob/main/themes/plex/plex.css#L7) one `background-image: url("./background.png");` +The [body](https://github.com/stashapp/CommunityScripts/blob/main/themes/Theme-Plex/plex.css#L7) one `background-image: url("./background.png");` -The [root](https://github.com/stashapp/CommunityScripts/blob/main/themes/plex/plex.css#L18) one `background: rgba(0, 0, 0, 0) url("./noise.png") repeat scroll 0% 0%;` +The [root](https://github.com/stashapp/CommunityScripts/blob/main/themes/Theme-Plex/plex.css#L18) one `background: rgba(0, 0, 0, 0) url("./noise.png") repeat scroll 0% 0%;` diff --git a/themes/plex/plex.css b/themes/Theme-Plex/Theme-Plex.css similarity index 100% rename from themes/plex/plex.css rename to themes/Theme-Plex/Theme-Plex.css diff --git a/themes/Theme-Plex/Theme-Plex.yml b/themes/Theme-Plex/Theme-Plex.yml new file mode 100644 index 00000000..b6983933 --- /dev/null +++ b/themes/Theme-Plex/Theme-Plex.yml @@ -0,0 +1,6 @@ +name: Theme - Plex +description: Plex Theme for Stash by Fidelio 2020 +version: 1.0.5 +ui: + css: + - Theme-Plex.css diff --git a/themes/Theme-Pulsar/Theme-Pulsar.css b/themes/Theme-Pulsar/Theme-Pulsar.css new file mode 100644 index 00000000..5574024d --- /dev/null +++ b/themes/Theme-Pulsar/Theme-Pulsar.css @@ -0,0 +1,1787 @@ +/* StashApp Pulsar Theme - Fonzie 2020-21 v1.8.1 */ +/* ---------------------------------------------------- */ +/* --------- Updated to Stash version 0.12.0 ---------- */ + +/* + Bug Fixes: Overlap of Help & Ssettings" buttons in the navbar, as well + as the Identify task + + Complete overhaul of the Settings page + + Bug Fix: Background-color in the navigation bar + + Adjustments to version 0.10.0 which includes moving the movie-, image- + and gallery-counter to the bottom of the performer image when you hover + over the card, and increasing the size of the star rating in the highest + zoom level. + + +*/ + + + +/* ===================== General ===================== */ + +body { + background-image:url("https://i.imgur.com/gQtSoev.jpg"); /* Aphonus */ +/* background-image:url("https://i.imgur.com/6BBd6aa.jpg"); /* Plex */ +/* background-image:url("https://i.imgur.com/xAzxryr.jpg"); /* Almora */ +/* background-image:url("https://i.imgur.com/0iN75zD.jpg"); /* Dacirus */ +/* background-image:url("https://i.imgur.com/g5ECcdD.jpg"); /* Drionope */ +/* background-image:url("https://i.imgur.com/dhVsc3d.jpg"); /* Elein */ +/* background-image:url("https://i.imgur.com/B5hdvQG.jpg"); /* FreePhion */ +/* background-image:url("https://i.imgur.com/LcSat6V.jpg"); /* Lilac */ +/* background-image:url("https://i.imgur.com/kn9wixj.jpg"); /* Nolrirus */ +/* background-image:url("https://i.imgur.com/190rDim.jpg"); /* Ongion */ +/* background-image:url("https://i.imgur.com/IpvdJVn.jpg"); /* PurpleRough */ +/* background-image:url("https://i.imgur.com/hAHylub.jpg"); /* Tesioria */ +/* background-image:url("https://i.imgur.com/QKiFSvE.jpg"); /* Ichix */ +/* background-image:url("https://i.imgur.com/8cIqGWj.jpg"); /* SeaGreen */ +/* background-image:url("https://i.imgur.com/WNXNwV3.jpg"); /* BrownBlur */ +/* background-image:url("./custom/background.jpg"); /* Local Background */ + + font-family:Helvetica, Verdana; + width: 100%; + height: 100%; + padding: 0 0 0; + background-size: cover; + background-repeat: no-repeat; + background-color:#127aa5; + background-attachment: fixed; + background-position: center; + color: #f9fbfd; +} + +h1,h2,h3,h4,h5,h6 { font-family:Helvetica, Verdana;} +:root { + --HeaderFont: Helvetica, "Helvetica Neue", "The Sans", "Segoe UI"; + --std-txt-shadow: 2px 2px 1px #000; + --light-txt-shadow: 1px 2px 1px #222; + --white: #ffffff; + --stars: url("https://i.imgur.com/YM1nCqo.png"); +} + + +/* --- The Home button in the top left corner of each page. Remove the last 3 lines if you don't like the logo --- */ +button.minimal.brand-link.d-none.d-md-inline-block.btn.btn-primary, +button.minimal.brand-link.d-inline-block.btn.btn-primary { + text-transform: uppercase; + font-weight: bold; + margin-left:1px; + background-image:url("./favicon.ico"); + padding-left:40px; + background-repeat: no-repeat; +} + +/* --- Makes the background of the Navigation Bar at the Top half-transparent --- */ +nav.bg-dark {background: rgba(10, 20, 25, 0.50)!important;} +.bg-dark {background:none !important;background-color:none !Important} +.form-group .bg-dark {background: rgba(10, 20, 25, 0.20)!important;} + +.navbar-buttons.navbar-nav a.nav-utility {margin-right:9px} + +/* --- The space between the Navigation Bar and the rest of the page --- */ +.main { margin-top:18px } +.top-nav { padding: .13rem 1rem; } + + +/* --- Changes how the Bar at the top of the page behaves --- */ +.fixed-bottom, .fixed-top { position: relative !important; top:0px !important} + + +/* The pagination at the top and at the bottom of the Scenes/Performer/Images/... pages; +transparent background, rounded corners, etc. */ +.filter-container, .operation-container { + background-color: rgba(0, 0, 0, .22); + box-shadow: none; + margin-top: 6px; + border-radius: 5px; + padding: 5px; +} + + +/* --- Changes the space between the button in the top right corner of the page --- */ +.order-2 button { margin-left: 4px } + +/* --- Space between the items in the Nav bar --- */ +.nav-link > .minimal { margin: 0px;} + + +/* Each item on the Scenes/Performers/Tags/... pages */ +.card { + padding: 20px; + margin: 4px 0.5% 14px; + /* --- Adds a glowing shimmer effect --- */ + background-image: linear-gradient(130deg, rgba(60, 70, 85,0.21), rgba(150, 155, 160,0.30), rgba(35, 40, 45,0.22), rgba(160, 160, 165,0.21), rgba(50, 60, 65,0.30)); + background-color: rgba(16, 20, 25, .25); + box-shadow: 2px 2px 6px rgba(0, 0, 0, .55); + /* --- Increases the rounded borders of each item on the Scenes/Performers/... pages for 6px in 10px --- */ + border-radius: 10px; +} + +/* --- Removes the glowing shimmer effect on the start page & the settings for readability purpose --- */ +.mx-auto.card, .changelog-version.card { + background-image: none !important; + background-color: rgba(16, 20, 25, .40) !important; +} + +/* --- Color that is used within .card secments --- */ +.text-muted { color: #f0f0f0 !important} + + +.bg-secondary { + background: none; + background-color: rgba(10, 25, 30, .62) !important; +} + +.text-white { color: #f0f0f0 } +.border-secondary { border-color: #2f3335 } + +.btn-secondary.filter-item.col-1.d-none.d-sm-inline.form-control { + background-color: rgba(0, 0, 0, .08); +} + +/* --- Changes the color and the background of the buttons and elements in the toolbar (Search, Sort by, # of Items, etc.) --- */ +.btn-secondary { + color: #f2f2f2; + background-color: rgba(0, 0, 0, .08); + border-color: #3c3f45; +} + +a { color: hsla(0, 10%, 95%, .75);} + + + +/* --- Changes the color of the active page in the Navgation Bar --- */ +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + background-color: rgba(22, 50, 60, .75); + border-color: #fff; +} + +/* --- No border of the active element in the Navgation Bar --- */ +.btn-primary.focus, +.btn-primary:focus, +.btn-primary:not(:disabled):not(.disabled).active:focus, +.btn-primary:not(:disabled):not(.disabled):active:focus, +.show>.btn-primary.dropdown-toggle:focus {box-shadow: none;} + +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + border-color: #eee; +} + +.container-fluid,.container-lg,.container-md,.container-sm,.container-xl { + width: 100%; + margin-right: 0px; + margin-left: 0px; +} + + + + + +/* ===================== Performer ========================= */ + + +/* --- 0.90 - Section moves Movie-, Image- & Gallery-Count to the bottom of the performer image when hovered over --- */ + +.performer-card .card-popovers .movie-count, +.performer-card .card-popovers .image-count, +.performer-card .card-popovers .gallery-count +{ + z-index:300; + position:absolute; + top:-270%; + opacity:0.0; +} + +/* --- Highlight the bottom of the performer card when hovered over --- */ +.performer-card.grid-card:hover { + background-image: linear-gradient(130deg, rgba(50, 60, 75,0.25), rgba(150, 155, 160,0.32), rgba(35, 40, 45,0.26), rgba(160, 160, 165,0.27), rgba(50, 60, 65,0.37)); + background-color: rgba(62, 72, 80, .26); +} + +/* --- When hovered over blend them in ---*/ +.performer-card.grid-card:hover .card-popovers .movie-count, +.performer-card.grid-card:hover .card-popovers .image-count, +.performer-card.grid-card:hover .card-popovers .gallery-count {opacity: 1.0;transition: opacity .7s;} + +/* --- 3 items gets a shadow ---*/ +.performer-card .card-section .movie-count span, +.performer-card .card-section .movie-count button.minimal, +.performer-card .card-section .image-count span, +.performer-card .card-section .image-count button.minimal, +.performer-card .card-section .gallery-count span, +.performer-card .card-section .gallery-count button.minimal +{text-shadow: 2px 2px 1px #000, 1px 1px 1px #000, 4px 4px 5px #333, 9px 0px 5px #333, -3px 2px 4px #333, -7px 0px 5px #333, +10px 2px 5px #000, 4px 14px 5px #333, 9px 0px 3px #333, -7px 2px 4px #333, -17px 0px 5px #333, -1px -9px 5px #333, 3px -8px 6px #444; +} + +.performer-card .card-section .movie-count .svg-inline--fa.fa-w-16 { + box-shadow: 1px 1px 1px rgba(0, 0, 0, .99), 1px 1px 3px rgba(0,0,0, .70), -5px 2px 5px rgba(0, 0, 0, .55); +} + +/* --- Positioning of the 3 items ---*/ +.performer-card .card-popovers .movie-count {left:0.2%;} +.performer-card .card-popovers .image-count {left:32.8%} +.performer-card .card-popovers .gallery-count {right:1.3%} + +.performer-card .movie-count a.minimal:hover:not(:disabled), .performer-card .movie-count button.minimal:hover:not(:disabled), +.performer-card .image-count a.minimal:hover:not(:disabled), .performer-card .image-count button.minimal:hover:not(:disabled), +.performer-card .gallery-count a.minimal:hover:not(:disabled), .performer-card .gallery-count button.minimal:hover:not(:disabled) +{ + background-color:rgba(20,80,110,0.92); + color:#fff; +} + +/* --- Affects the Scenes- and Tags-Counter --- */ +a.minimal:hover:not(:disabled), button.minimal:hover:not(:disabled) {background: rgba(138,155,168,.25);color:#fff;} +div.performer-card div.card-popovers +{ + margin-bottom:-3px; + margin-left:1%; + margin-top:-4px; + margin-right: -3px; + justify-content: flex-end; + text-align:right; +} + +div.card-section hr {display:none} + + +/* --- Changes the width of the Performer Card from 280px to a dynamic system and therefore the size of the image --- */ +/* --- In Full screen HD 1920x1080 you now see 8 performers per row instead of 6 --- */ +/*.performer-card-image, .performer-card, .card-image { min-width: 160px; width: calc(108px + 10.625vw / 2); max-width: 230px } */ +/*.performer-card-image, .performer-card, .card-image { min-width: 160px; width: calc(108px + 19vw / 3.6);width:212px; max-width: 230px } */ +.performer-card-image, .performer-card, .card-image { min-width: 160px; width: calc(100px + 11.2vw / 1.92);max-width: 230px } + + +/* --- Changes the height of the Performer Card to keep the 2x3 picture ratio --- */ +/*.performer-card-image, .justify-content-center .card-image { min-height:240px; height: calc((108px + 10.625vw / 2) * 1.5); max-height: 345px} */ +.performer-card-image, .justify-content-center .card-image { min-height:240px; height: calc((112px + 19vw / 3.6) * 1.5); max-height: 345px;} +.performer-card-image, .justify-content-center .card-image { min-height:240px; height: calc((100px + 11.2vw / 1.92) * 1.5); max-height: 345px;} + +@media (max-width: 575px), (min-width: 1200px) { +.scene-performers .performer-card-image { height: auto; } +.scene-performers .performer-card { width: auto; } +} + + +/* --- Fixes an issue of the card when watching a scene --- */ +.image-section { display: cover;} + +/* --- The name of the Performer. Different font, less space to the left & to the top, Text-Shadow --- */ +.text-truncate, .card.performer-card .TruncatedText { + margin-left:-1.5%; + margin-top: -2px; + width: 120%; + font-family: var(--HeaderFont); + font-size: 112%; + line-height:130%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} + +/* --- Makes the Performer Name smaller when the screen is too small --- */ +@media (max-width: 1200px) { .card.performer-card .TruncatedText { font-size: 104%; } } + + + +/* --- Moves the Flag icon from the right side of the card to the left and makes the Flag image a little bit bigger --- */ +.performer-card .flag-icon { + height: 2rem; + left: 0.6rem; + bottom: 0.10rem; + width: 28px; +} + +/* --- Age and # of Scenes move from the left side to the right of the card --- */ +.performer-card .text-muted {text-align:right;margin-top:-2px;margin-bottom:1px;width:44%;margin-left:56%} + + +/* --- Minimum height for the section in case Age and Nationality is missing and elements would overlap --- */ +.performer-card .card-section {min-height:82px} + +/* --- "removes" the term 'old.' from "xx years old." when the resolution gets to small --- */ +@media (max-width: 1700px) { +div.card.performer-card .text-muted {text-align:right;margin-top:-2px;margin-bottom:1px;margin-right:-33px; height:20px; max-height:20px; overflow: hidden; +} +} + +/* --- To prevent overlapping in the performer card when watching a scene --- */ +@media (max-width: 2000px) { +.tab-content div.card.performer-card .text-muted {margin-top:22px;margin-right:-3px} +.tab-content .performer-card.card .rating-1, +.tab-content .performer-card.card .rating-2, +.tab-content .performer-card.card .rating-3, +.tab-content .performer-card.card .rating-4, +.tab-content .performer-card.card .rating-5 {bottom: 53px !important;} +} + + +/* --- Text Shadow for the "Stars in x scenes" link --- */ +div.card.performer-card div.text-muted a {text-shadow: 1px 2px 2px #333} + +/* --- Makes the card section (Name, Age, Flag, # of scenes) more compact --- */ +.card-section { margin-bottom: -7px !important; padding: .5rem 0.7rem 0 !important;} +.card-section span {margin-bottom:3px} +@media (max-width: 1500px) { .card-section span {font-size:13px} } + +div.card-section hr {display:none} + + + + +/* --- Changes regarding the Favorite <3 --- */ +.performer-card .favorite { + color: #f33; + -webkit-filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, .95)); + filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, .95)); + right: 3px; + top: 5px; +} + + + +/* --- Turns the Rating Banner in the top left corner into a Star Rating under the performer name --- */ +.performer-card.card .rating-1, .performer-card.card .rating-2, .performer-card.card .rating-3, +.performer-card.card .rating-4, .performer-card.card .rating-5 +{ + background:none; + background-size: 97px 18px; + background-image:var(--stars); + -webkit-transform:rotate(0deg); + transform:rotate(0deg); + padding:0; + padding-bottom:1px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, .00); + left:6px; + width:97px; + height:18px; + text-align:left; + position:absolute; + top:auto; + bottom: 34px; + font-size:0.001rem; +} + +/* --- Display only X amount of stars -- */ +div.performer-card.card .rating-banner.rating-1 {width:20px} +div.performer-card.card .rating-banner.rating-2 {width:39px} +div.performer-card.card .rating-banner.rating-3 {width:59px} +div.performer-card.card .rating-banner.rating-4 {width:78px} +div.performer-card.card .rating-banner.rating-5 {width:97px} + + +.performer-card .btn {padding: .375rem .013rem} +.performer-card .btn {padding: .34rem .25rem} +.performer-card .fa-icon {margin: 0 2px} +.performer-card .card-popovers .fa-icon {margin-right: 4px} +.performer-card .svg-inline--fa.fa-w-18, .performer-card .svg-inline--fa.fa-w-16 {height: 0.88em} +.performer-card .favorite .svg-inline--fa.fa-w-16 {height:1.5rem} + + +.performer-card .card-popovers .btn-primary { + margin: 0 0px 0 6px; +} + + + +/* --- PerformerTagger Changes --- */ + +.PerformerTagger-performer { + background-image: linear-gradient(130deg, rgba(50, 60, 75,0.25), rgba(150, 155, 160,0.32), rgba(35, 40, 45,0.26), rgba(160, 160, 165,0.27), rgba(50, 60, 65,0.37)); + background-color: rgba(16, 20, 25, .23); + box-shadow: 2px 2px 6px rgba(0, 0, 0, .70); + border-radius: 8px; + margin: 1.1%; + } + +.tagger-container .input-group-text {background:none;border:0;margin-right:5px;padding-left:0} +.PerformerTagger-details { margin-left: 1.25rem; width:23.5rem;} + +.tagger-container .btn-link{text-shadow: 1px 2px 3px #000;} +.tagger-container, .PerformerTagger { max-width: 1850px;} + +.PerformerTagger-header h2 { + font-family: Helvetica, "Helvetica Neue", "Segoe UI" !important; + font-size: 2rem; + line-height:130%; + font-weight:bold; + text-shadow: 2px 2px 1px #000 !important +} + +.PerformerTagger-thumb {height: 50px;} + +.modal-backdrop { background-color: rgba(16, 20, 25, .25);} +.modal-backdrop.show { opacity: 0.1; } + +.performer-create-modal { max-width: 1300px; font-family: Helvetica, "Helvetica Neue", "Segoe UI" !important; } +.performer-create-modal .image-selection .performer-image { height: 95%; } +.performer-create-modal .image-selection {height: 485px;} + +.performer-create-modal .no-gutters .TruncatedText { + font-family: var(--HeaderFont); + font-size: 115%; + padding-top:2px; + line-height:120%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} +.performer-create-modal-field strong {margin-left: 6px} +.modal-footer {border-top: 0} + + + + + + +/* ========================= Performer Page ================================= */ +/* === The page that you see when you click on the picture of a Performer === */ + +/* --- The picture of the Performer on the left. 3D effect thanks to background shadows and more rounded corners --- */ +#performer-page .performer-image-container .performer +{ + background-color: rgba(0, 0, 0, .48); + box-shadow: 6px 6px 11px rgba(0, 10, 10, .62); + border-radius: 14px !important; +} + +/* --- Without this the shadow at the bottom from the previous Selector will not be correctly displayed --- */ +.performer-image-container {padding-bottom: 11px} + + +/* --- The following 15 Selectors change the way the details box is displayed --- */ +#performer-details-tabpane-details .text-input, #performer-details-tabpane-details .text-input:disabled, +#performer-details-tabpane-details .text-input[readonly] {background-color: rgba(16,22,26,0.0);} +#performer-details-tabpane-details a { text-shadow: var(--light-txt-shadow)} + +.text-input, input.form-control-plaintext { background-color:none;} +#performer-details .input-control, .text-input {box-shadow: none;} + +div.react-select__control, #performer-details-tabpane-details {background-color: rgba(15,20,30,0.26); max-width:1000px} +#performer-details-tabpane-details {border-radius: 10px} +#performer-details-tabpane-edit {max-width:1000px} + +div.react-select__control .css-12jo7m5 {text-shadow: none; } + +@media (min-width: 1200px) { + #performer-details-tabpane-details td { padding: 9px; } + table#performer-details tbody tr td:nth-child(1), td:first-child {padding-left: 22px; width: 185px;} +} +@media (max-width: 1200px) { + table#performer-details tbody tr td:nth-child(1), td:first-child {padding-left: 11px; } + #performer-page .performer-head { margin-bottom: 1rem; } + #performer-page { margin: 0 -6px 0 -15px; } +} +#performer-details-tabpane-details tr:nth-child(odd) { background-color: rgba(16,22,26,0.1); } +table#performer-details {color:#FFF; text-shadow: 1px 1px 1px #000;} + + + +/* --- Changes the way the name of the performer is displayed --- */ +.performer-head h2 {font-family: var(--HeaderFont); font-weight:bold; text-shadow: 2px 2px 2px #111 } + +/* --- Leave some space between the name and the Fav/Link/Twitter/IG icons --- */ +#performer-page .performer-head .name-icons {margin-left: 22px} + +/* --- Highlighter for active Details/Scenes/Images/Edit/Operations --- */ +.nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { + background-color: rgba(5,30,35,0.46); +} + + +/* --- Changes the display of Performer Details Tab in the 0.9 version are arranged --- */ +#performer-details-tabpane-details dl.row, dl.details-list dt, dl.details-list dd{ margin:0 0px;padding: 8px 10px 9px 14px} +#performer-details-tabpane-details dl.row:nth-child(odd), +#performer-details-tabpane-details dl.details-list dt:nth-of-type(odd), +#performer-details-tabpane-details dl.details-list dd:nth-of-type(odd) { background-color: rgba(16,22,26,0.1);} +#performer-details-tabpane-details dt.col-xl-2, +#performer-details-tabpane-details dl.details-list dt { text-shadow: var(--std-txt-shadow); font-weight: normal;} +#performer-details-tabpane-details ul.pl-0 {margin-bottom: 0rem;} +#performer-details-tabpane-details dl.details-list { grid-column-gap: 0} + + +/* --- Resets the fields in Performer Edit Tab in the 0.5 developmental version back to way it was in the 0.5 version --- */ +#performer-edit {margin:0 0 0 10px} +#performer-edit .col-sm-auto, #performer-edit .col-sm-6, #performer-edit .col-md-auto, #performer-edit .col-lg-6, #performer-edit .col-sm-4, #performer-edit .col-sm-8 { width: 100%;max-width: 100%; flex: 0 0 100%; } +#performer-edit .col-sm-auto div, #performer-edit label.form-label { float:left; width:17%;} +#performer-edit .col-sm-auto div, #performer-edit label.form-label { font-weight:normal; color: #FFF; text-shadow: var(--std-txt-shadow); } + +#performer-edit select.form-control, #performer-edit .input-group, #performer-edit .text-input.form-control { float:right; width:83%; } +#performer-edit .form-group, .col-12 button.mr-2 {margin-bottom: 0.35rem} +#performer-edit .mt-3 label.form-label { float:none; width:auto; } + +#performer-edit select.form-control, #performer-edit .input-group, #performer-edit .text-input.form-control {width: 100%;} +#performer-edit textarea.text-input {min-height: 9ex;} + +#performer-edit .form-group:nth-child(17) .text-input.form-control {width:85%;} + +@media (max-width: 750px) { +#performer-edit .col-sm-auto div, #performer-edit label.form-label { float:left; width:22%;} +#performer-edit select.form-control, #performer-edit .input-group, #performer-edit .text-input.form-control { float:right; width:78%; } +} + +@media (max-width: 500px) { +#performer-edit .col-sm-auto div, #performer-edit label.form-label { float:left; width:60%;} +#performer-edit li.mb-1, +#performer-edit select.form-control, +#performer-edit .input-group, #performer-edit .text-input.form-control { float:left; width:89%; } +} + +#performer-edit .form-group .mr-2 {margin-right:0!important} + + + + + + +/* ======================= Scenes ======================== */ + + +/* --- Remove the comments if you don't want to see the Description Text of the scenes --- */ +/* .card-section p {display:none} */ + + +/* --- Remove the comments if you don't want to see the Resolution of the Video (480p, 540p, 720p, 1080p) --- */ + .overlay-resolution {display:none} + + + +/* --- The name of the Scene. Different font, less space to the left and to the top, Text-Shadow --- */ +h5.card-section-title, .scene-tabs .scene-header { + font-family: var(--HeaderFont); + font-size: 1.25rem; + font-weight:bold; + line-height:132%; + text-shadow: var(--std-txt-shadow); +} +.scene-tabs .scene-header { font-size: 24px; margin-bottom:16px } +.scene-tabs .studio-logo { margin-top: 0} + +#TruncatedText .tooltip-inner {width:365px; max-width:365px} +.tooltip-inner { font-family: var(--HeaderFont); + background-color: rgba(16, 20, 25, .99); + box-shadow: 2px 2px 6px rgba(0, 0, 0, .55); + font-weight:bold;font-size:14px;} + +/* --- Removes the horizontal line that separates the date/description text from the Tags/Performer/etc. icons --- */ +.scene-card.card hr, .scene-card.card>hr{ border-top: 0px solid rgba(0,0,0,.1); } + + +/* --- Changes regarding the Scene Logo --- */ +.scene-studio-overlay { + opacity: .80; + top: -1px; + right: 2px; +} + +/* --- The Resolution and the Time/Length of the video in the bottom right corner to make it easier to read --- */ +.scene-specs-overlay { + font-family: Arial, Verdana,"Segoe UI" !important; + bottom:1px; + color: #FFF; + font-weight: bold; + bottom:1.4%; + letter-spacing: 0.035rem; + text-shadow: 2px 2px 1px #000, 4px 4px 5px #444, 7px 0px 5px #444, -3px 2px 5px #444, -5px 0px 5px #444, -1px -4px 5px #444, 3px -2px 6px #444; +} +.overlay-resolution {color:#eee;} + +/* --- Changes the spaces between the items on the Scenes page --- */ +.zoom-0 {margin: 4px 0.50% 10px} + + +.scene-card-link {height:195px; overflow:hidden;} + + +/* --- Tightens the space between the Tags-, Performer-, O-Counter-, Gallery- and Movie-Icons --- */ +.btn-primary { margin:0 -3px 0 -9px} + +/* --- Moves the Tags-, Performer-, O-Counter-, Gallery- and Movie-Icon from below the description to the bottom right corner of the card --- */ +.scene-popovers, .card-popovers { + min-width:0; + margin-bottom: 3px; + margin-top:-40px; + justify-content: flex-end; +} + +/* --- Adds an invisible dot after the description text, Also leaves ~80 pixels space to enforce a line break, +so it leaves some space in the bottom right corner of the card for the icons in the Selector above --- */ +.card-section p:after +{ + font-size: 1px; + color: rgba(0,0,0, .01); + padding-right: 3.2vw; + margin-right: 2.8vw; + content: " "; +} + + + + +/* -- The whole section replaces the ratings banner with a star rating in the bottom left corner --- */ +.scene-card.card .rating-1 {width:22px} +.scene-card.card .rating-2 {width:43px} +.scene-card.card .rating-3 {width:65px} +.scene-card.card .rating-4 {width:86px} +.scene-card.card .rating-5 {background:none; width:108px} +.rating-1, .rating-2, .rating-3, .rating-4, .scene-card.card .rating-5 { + background:none; + background-image:var(--stars); + height:20px; + background-size: 108px 20px; +} + +.scene-card.card .rating-banner { + padding:0; + left:5px; + top:89%; + background-position: left; + font-size: .01rem; + -webkit-transform: rotate(0deg); + transform: rotate(0deg); +} + + +.scene-card.zoom-0.grid-card.card .rating-banner {top: 87%} +.scene-card.zoom-2.grid-card.card .rating-banner {top: 90%} +.scene-card.zoom-3.grid-card.card .rating-banner {top: 92%} + +.scene-card.zoom-3.grid-card.card .rating-1, .scene-card.zoom-3.grid-card.card .rating-2, .scene-card.zoom-3.grid-card.card .rating-3, .scene-card.zoom-3.grid-card.card .rating-4, .scene-card.zoom-3.grid-card.card .rating-5 { + background:none; + background-image:var(--stars); + height:28px; + background-size: 151px 28px; +} + +.scene-card.zoom-3.grid-card.card .rating-1 {width:30px} +.scene-card.zoom-3.grid-card.card .rating-2 {width:60px} +.scene-card.zoom-3.grid-card.card .rating-3 {width:91px} +.scene-card.zoom-3.grid-card.card .rating-4 {width:121px} +.scene-card.zoom-3.grid-card.card .rating-5 {width:151px} + + + +/* --- Improves how the Preview Videos in the Wall View are displayed --- */ +.wall-item-container {width: 100%; background-color: rgba(0, 0, 0, .10); overflow:hidden } +.wall-item-media { height:100%; background-color: rgba(0, 0, 0, .0);overflow:hidden } +.wall-item-anchor { width: 102%; overflow:hidden } +.wall-item-text {margin-bottom:0px; color: #111; text-shadow: 1px 1px 1px #fff } + + +.scene-popovers .fa-icon {margin-right: 2px;} + +/* --- Changes the Organized Button color when watching a video. Organized = Green, Not Organized = Red --- */ +.organized-button.not-organized { color: rgba(207,10,20,.8); } +.organized-button.organized { color: #06e62e;} + + +/* --- Changes the font in the File Info section --- */ +div.scene-file-info .TruncatedText, div.scene-file-info .text-truncate { + margin-left:-1.5%; + margin-top: -1px; + width: 120%; + font-family: var(--HeaderFont); + font-size: 110%; + line-height:120%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} + + +#scene-edit-details .pl-0 { + padding-left: 10px!important; +} + + +/* Zoom 0 */ +.zoom-0 { width:290px} +.zoom-0 .video-section {height:181px;} +.zoom-0 .scene-card-preview-image, .zoom-0 .scene-card-preview { height:195px; } +.zoom-0 .scene-card-preview, .zoom-0 .scene-card-preview-video, .zoom-0 .scene-card-video { + width: 290px; + min-height:181px; + max-height: 200px; +} + +/* Zoom 1 */ +.zoom-1 { min-width: 300px; width: calc(234px + 24vw /3.84);max-width: 430px} +/* Improves the way the scene picture is displayed when the resolution isn't 16:9 (e.g. 4:3) --- */ +.zoom-1 .video-section {height:calc((234px + 24vw / 3.84)/1.63);max-height: 258px} +.zoom-1 .scene-card-preview-image, .zoom-1 .scene-card-preview { height:98%; max-height: 260px} + +.zoom-1 .scene-card-preview, .zoom-1 .scene-card-preview-video, .zoom-1 .scene-card-video { + min-width: 300px; width: calc(228px + 17vw / 1.92);max-width: 470px; + height:calc((234px + 26vw / 3.84)/1.63); + max-height: 265px; +} + +/* Zoom 2 */ +.zoom-2 { min-width: 350px; width: calc(315px + 26vw / 3.84);max-width: 495px} +.zoom-2 .video-section {height:calc((334px + 26vw / 3.84) /1.63);max-height:295px} +.zoom-2 .scene-card-preview-image, .zoom-2 .scene-card-preview { height:calc((334px + 26vw / 3.84) /1.63); max-height:292px} + +.zoom-2 .scene-card-preview, .zoom-2 .scene-card-preview-video, .zoom-2 .scene-card-video { + min-width: 350px; width: calc(332px + 28vw / 3.84);max-width: 530px; + height:calc((335px + 28vw / 3.84) /1.63); + max-height: 298px; +} + + +/* Zoom 3 */ +.zoom-3 { min-width: 400px; width: calc(530px + 18vw / 5.76);max-width: 590px} +.zoom-3 .video-section {height:375px;} +.zoom-3 .scene-card-preview-image, .zoom-3 .scene-card-preview { height:395px; } +.zoom-3 .scene-card-preview, .zoom-3 .scene-card-preview-video, .zoom-3 .scene-card-video { + width: 600px; + min-height:385px; + max-height: 400px; +} + + +.zoom-0 .video-section, .zoom-1 .video-section, .zoom-2 .video-section, .zoom-3 .video-section +{object-fit: cover !important;overflow:hidden;} + +.zoom-0 .scene-card-preview, .zoom-0 .scene-card-preview-video, .zoom-0 .scene-card-video, +.zoom-1 .scene-card-preview, .zoom-1 .scene-card-preview-video, .zoom-1 .scene-card-video, +.zoom-2 .scene-card-preview, .zoom-2 .scene-card-preview-video, .zoom-2 .scene-card-video, +.zoom-3 .scene-card-preview, .zoom-3 .scene-card-preview-video, .zoom-3 .scene-card-video { + object-fit: cover !important; + margin-top:-2%; + margin-left:-6px; + transform: scale(1.04); +} + +/* --- Shrink the Player Height just a little bit to avoid the scroll bar --- */ +#jwplayer-container { height: calc(99.5vh - 4rem);} +.scene-tabs { max-height: calc(99vh - 4rem); } + +div.tagger-container .btn-link { + font-family: var(--HeaderFont); + font-size: 110%; + color: #ddf; + text-shadow: var(--std-txt-shadow); +} + + +/* --- Changes the color of the scrape button when editing a scene --- */ +.scrape-url-button{background-color: rgba(20,120,20,.50);} +.scrape-url-button:hover{background-color: rgba(20,150,20,.65);} +.scrape-url-button:disabled { background-color: rgba(30,00,00,.40); } + + +.scene-tabs .scene-header {margin-top: 0;margin-bottom: 15px} +.scene-tabs h1.text-center {margin-bottom: 30px} + +#queue-viewer .current {background-color: rgba(25,60,40,0.40);} +#queue-viewer .mb-2:hover, #queue-viewer .my-2:hover {background-color: rgba(15,20,30,0.28);} + +#scene-edit-details .edit-buttons-container { + background-color: rgba(0,0,0,0.0); + position: relative; + margin-bottom:15px; +} + +#scene-edit-details .form-group {margin-bottom:0.65rem;} + + + + + +/* ============== Studio ================= */ + + +.studio-card { padding: 0 4px 14px;} + +.studio-details input.form-control-plaintext { background-color: rgba(16,22,26,.0); } +.studio-details .react-select__control--is-disabled { background: none; border:0} + +.studio-details .form-group, .studio-details td { padding: 8px; } +.studio-details table td:nth-child(1) {color:#FFF; text-shadow: 1px 1px 1px #000;} + +.studio-card-image {max-height: 175px; height:175px} +.studio-card-image {min-width: 260px; width: calc(244px + 19vw / 3.8); max-width: 360px; margin: 0 1px;} +.studio-card .card-section { margin-top: 22px;} + +@media (min-width: 1200px) { +.pl-xl-5, .px-xl-5 { + padding-left: 1rem!important; + padding-right: 1rem!important; +} } + +.no-gutters .TruncatedText, .tag-card .TruncatedText, div.card.studio-card .TruncatedText, .tagger-container .TruncatedText { + font-family: var(--HeaderFont); + font-size: 125%; + line-height:125%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} + +.no-gutters .TruncatedText {font-size: 115%;} + +/* --- The following 15 Selectors modify the info box on the left after clicking on a movie --- */ +.studio-details .text-input, #performer-details-tabpane-details .text-input:disabled, +.studio-details .text-input[readonly] {background-color: rgba(16,22,26,0.0);} + +.text-input, input.form-control-plaintext { background-color:none;} +.studio-details .input-control, .text-input {box-shadow: none;} + +.studio-details table { margin-top: 20px; background-color: rgba(15,20,30,0.20); border-radius: 10px; margin-bottom:20px;} +.studio-details .form-group {background-color: rgba(15,20,30,0.20); margin:0;} + +.studio-details table div.react-select__control {background: none; border: 0px;margin:0} +.studio-details table .css-1hwfws3 { padding:0px; } + +.studio-details .form-group, .movie-details td { padding: 8px; } +.studio-details .form-group td:nth-child(1), +.studio-details table tbody tr td:nth-child(1), td:first-child {padding-left: 12px; width: 130px;} + +.studio-details table tr:nth-child(odd) { background-color: rgba(16,22,26,0.1); } +.studio-details .form-group, .studio-details table td:nth-child(1) {color:#FFF; text-shadow: 1px 1px 1px #000;} + + +.studio-card.card .rating-1, .studio-card.card .rating-2, .studio-card.card .rating-3, +.studio-card.card .rating-4, .studio-card.card .rating-5 +{ + background:none; + height: 25px; + background-size: 135px 25px; + background-image:var(--stars); + -webkit-transform:rotate(0deg); + transform:rotate(0deg); + padding:0; + padding-bottom:1px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, .00); + left:10px; + text-align:left; + position:absolute; + top:auto; + bottom: 24% !important; + font-size:0.001rem; +} + +.studio-card.card .rating-5{width:135px;} +.studio-card.card .rating-4{width:109px;} +.studio-card.card .rating-3{width:81px;} +.studio-card.card .rating-2{width:55px;} +.studio-card.card .rating-1{width:28px;} + +div.studio-card.card .card-popovers { margin-top:-34px;margin-right:-7px} +.studio-card.card .card-section div:nth-child(2) {margin-bottom:6px;margin-top:-3px;} + +div.studio-details dl.details-list {grid-column-gap:0} +.studio-details dt, .studio-details dd {padding: 6px 0 8px 8px} + + + + + + + +/* ============== TAGS =============== */ + +.tag-card.card hr, .tag-card.card>hr{border-top: 0px solid rgba(0,0,0,0.0)} + +.tag-card {margin: 4px 0.5% 10px; padding:0px;} + + +@media (min-width: 1200px){ +.row.pl-xl-5, .row.px-xl-5 { + padding-left: 0.2rem!important; + padding-right: 0.2rem!important; +} +} + +.tag-card.zoom-0 { + min-width: 230px; width: calc(205px + 18vw / 1.1); max-width: 354px; + min-height:168px; height:auto; +/* height:calc(130px + 14vw / 1.1); max-height:250px;*/ +} +.tag-card.zoom-0 .tag-card-image { min-height: 100px; max-height: 210px; height: calc(95px + 15vw / 1.1)} + +.tag-card.zoom-1 { + min-width: 260px; width: calc(238px + 25vw / 2.3); max-width: 460px; + min-height:193px; height:auto; max-height:335px; +} +.tag-card.zoom-1 .tag-card-image { min-height: 120px; max-height: 260px; height: calc(100px + 19vw / 2.3);} + +.tag-card.zoom-2 { + min-width: 290px; width: calc(280px + 38vw / 2.45); max-width: 650px; + min-height:170px; height:auto; max-height:505px; +} +.tag-card.zoom-2 .tag-card-image { min-height: 175px; max-height: 435px; height: calc(120px + 26vw / 2.45);} + +#tags .card {padding:0 0 10px 0; } +.tag-card-header {height:190px;overflow:hidden;} + +.zoom-0 .tab-pane .card-image { height:210px } +.zoom-0 .tag-card-image, .zoom-1 .tag-card-image, .zoom-2 .tag-card-image { +zoom: 101%; +object-fit: cover; +overflow:hidden; +width: 101%; +margin-top: -2px; +margin-left: -1%; +} + +.tag-card .scene-popovers, .tag-card .card-popovers { + width:60%; + margin-left:40%; + justify-content: flex-end; + float:right; + margin-bottom: 15px; + margin-top:-34px; + padding-left:17px; +} + +.tag-sub-tags,.tag-parent-tags {margin-bottom:8px} + + +/* --- Moves the Tag name into the Tag Picture --- */ +.tag-details .text-input[readonly] {background-color: rgba(0,0,0,.0)} +.tag-details .table td:first-child {display:none} +.tag-details .logo {margin-bottom: 12px;} + +.tag-details .form-control-plaintext, .tag-details h2 { + margin-top:-76px; + margin-left:0%; + font-weight: bold; + font-family: Helvetica, "Helvetica Neue", "Segoe UI" !important; + letter-spacing: 0.11rem; + font-size:44px; + text-shadow: 2px 2px 3px #111, 4px 4px 4px #282828, 6px 1px 4px #282828, -3px 3px 3px #444, -2px -2px 4px #282828; + text-align:center; +} +@media (max-width: 1300px) {.tag-details .form-control-plaintext {font-size:26px; margin-top:-50px;}} + +.tag-details .logo { min-width:300px} + + + + + +/* ============== MOVIES ============== */ + +/* --- Changes the width of the items so only the front cover is displayed. Also replaces the ratings banner with a star rating --- */ + +.movie-card .text-truncate, div.card.movie-card .TruncatedText { + font-size: 17px !important; + font-family: var(--HeaderFont); + text-shadow: var(--std-txt-shadow); + font-weight: bold; + max-width:210px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.movie-card.grid-card.card .card-section p {margin-bottom:-8px} +div.movie-card.grid-card.card .card-section {margin-bottom: -0px !important} +div.movie-card.grid-card.card .card-popovers { + padding-top:35px; + margin-bottom:-11px; + width:60%; + margin-left:40%; + justify-content:flex-end; + float:right; +} + +div.movie-card .card-section span {position:absolute;margin-top:-3px;margin-bottom:6px} + + + +.movie-card-header {height:320px} + +.movie-card-header .rating-banner { + font-size: .001rem; + padding: 8px 41px 6px; + line-height: 1.1rem; + transform: rotate(0deg); + left: 3px; + top: 317px !important; + height: 25px; + background-size: 135px 25px; + background-position: left; +} + +.movie-card-header .rating-1 {width:28px} +.movie-card-header .rating-2 {width:55px} +.movie-card-header .rating-3 {width:83px} +.movie-card-header .rating-4 {width:110px} +.movie-card-header .rating-5 {width:138px} + +.movie-card-header .rating-5 { + background:none; + background-image:var(--stars); + height: 25px; + background-size: 135px 25px; +} + +.movie-card-image { + height:345px; + max-height: 345px; + width:240px; +} + + + + +/* --- The following 15 Selectors modify the info box on the left after clicking on a movie --- */ +.movie-details .text-input, #performer-details-tabpane-details .text-input:disabled, +.movie-details .text-input[readonly] {background-color: rgba(16,22,26,0.0);} + +.text-input, input.form-control-plaintext { background-color:none;} +.movie-details .input-control, .text-input {box-shadow: none;} + +.movie-details table { margin-top: 20px; background-color: rgba(15,20,30,0.20); border-radius: 10px 10px 0px 0px; margin-bottom:0;} +.movie-details .form-group {background-color: rgba(15,20,30,0.20); margin:0;} + +.movie-details table div.react-select__control {background: none; border: 0px;margin:0} +.movie-details table .css-1hwfws3 { padding:0px; } + +.movie-details .form-group, .movie-details td { padding: 8px; } +.movie-details .form-group td:nth-child(1), +.movie-details table tbody tr td:nth-child(1), td:first-child {padding-left: 12px; width: 120px;} + +.movie-details table tr:nth-child(odd) { background-color: rgba(16,22,26,0.1); } +.movie-details .form-group, .movie-details table td:nth-child(1) {color:#FFF; text-shadow: 1px 1px 1px #000;} + + + +/* --- 0.60 dev adjustments --- */ +.studio-details .studio-details, .movie-details .movie-details {background-color: rgba(15,20,30,0.20); border-radius: 10px; margin-bottom:15px; } +.movie-details .movie-details dt.col-3 {padding:4px 0 4px 16px; width: 120px;} +.movie-details .movie-details dd.col-9 {padding:4px 16px 4px 3px;} +.studio-details dl.details-list dt:nth-of-type(odd), +.studio-details dl.details-list dd:nth-of-type(odd), +.movie-details dl.details-list dt:nth-of-type(odd), +.movie-details dl.details-list dd:nth-of-type(odd), +.movie-details dl.row:nth-child(odd) { background-color: rgba(16,22,26,0.1); margin-right:0px} +.movie-details dl.details-list { grid-column-gap: 0} +.studio-details h2, .movie-details .movie-details h2 { font-family: var(--HeaderFont);font-weight:bold;text-shadow: var(--std-txt-shadow);padding:7px 0 5px 12px;} + +.movie-details .movie-images {margin:0 5px 20px 5px;} +.movie-details .movie-images img {border-radius: 14px; max-height:580px;} +.movie-details .movie-image-container{ + margin:0.3rem; + margin-right:0.8rem; + background-color: rgba(0, 0, 0, .48); + box-shadow: 6px 6px 11px rgba(0, 10, 10, .62); +} + +form#movie-edit { margin-bottom:15px} + + + + + +/* ============== IMAGES ============== */ + +div.image-card .rating-banner { + font-size: .002rem; + padding: 8px 41px 6px; + line-height: 1.1rem; + transform: rotate(0deg); + left: 3px; + top: 72% !important; + height: 25px; + background-size: 135px 25px; + background-position: left; +} + +div.image-card .rating-1 {width:28px} +div.image-card .rating-2 {width:55px} +div.image-card .rating-3 {width:83px} +div.image-card .rating-4 {width:110px} +div.image-card .rating-5 {width:138px} + +div.image-card .rating-5 { + background:none; + background-image:var(--stars); + height: 25px; + background-size: 135px 25px; +} + +div.image-card .scene-popovers, div.image-card .card-popovers { + margin-top: -2px; + justify-content: flex-end; +} +div.image-card hr, .scene-card.card>hr{ + border-top: 0px solid rgba(0,0,0,.1); +} + + + + + + + +/* ============== GALLERIES ============== */ + +div.gallery-card hr, .scene-card.card>hr{ + border-top: 0px solid rgba(0,0,0,.1); +} + +div.gallery-card .rating-banner { + font-size: .002rem; + padding: 8px 4px 6px; + line-height: 1.1rem; + transform: rotate(0deg); + left: 3px; + top: 70% !important; + height: 25px; + background-size: 135px 25px; + background-position: left; +} + +div.gallery-card .rating-1 {width:28px !important} +div.gallery-card .rating-2 {width:55px !important} +div.gallery-card .rating-3 {width:83px !important} +div.gallery-card .rating-4 {width:110px} +div.gallery-card .rating-5 {width:137px} + +div.gallery-card .rating-5 { + background:none; + background-image:var(--stars); + height: 25px; + background-size: 135px 25px; +} + +div.gallery-card .scene-popovers, div.gallery-card .card-popovers { + margin-bottom: -8px; + margin-top: -48px; + justify-content: flex-end; +} + + + + + + + + +/* ============== MISC ============== */ + +/* --- When comments are removed the first paginationIndex ("1-x of XXX - time - size") will disappear --- */ +/* .paginationIndex:first-of-type {display:none} */ + + +.svg-inline--fa.fa-w-18 {width: 1.4em;} + +/* --- Makes the Zoom Slider on the Scenes, Images, Galleries and Tags pages longer and therefore easier to use --- */ +input[type=range].zoom-slider{ max-width:140px;width:140px; } + +/* --- Changes the zoom slider color --- */ +input[type=range]::-webkit-slider-runnable-track {background-color: #88afcc !important;} + + +.tag-details .logo { + background-color: rgba(0, 0, 0, .48); + box-shadow: 3px 3px 5px rgba(0, 0, 0, .42); + border-radius: 9px !important; +} + +.search-item { + background-color: none; + background-color: rgba(16,22,26,0.27); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + background-color: none; + background-color: rgba(16,22,26,0.67); +} + +/* --- Edit & Delete buttons when clicking on a studio, tag or movie --- */ +.details-edit {margin-left:3%} + +/* --- Adds a text shadow to the statistics on the startpage --- */ +.stats .title { + text-shadow: 2px 2px 4px #282828; +} + + +.popover { + padding:2px; + background-color: rgba(5,30,35,0.85); + box-shadow: 3px 3px 6px rgba(20, 20, 20, .8); +} +.hover-popover-content { + background-image: linear-gradient(160deg, rgba(230,255,240,0.80), rgba(120,130,155, 0.45), rgba(180,190,225, 0.45), rgba(120,130,165, 0.55), rgba(255,235,235,0.70)); + background-color: rgba(205,210,225,0.31) !important; +} + +.tag-item { + font: normal 13px "Lucida Grande", sans-serif, Arial, Verdana; + background-image: linear-gradient(210deg, rgba(30,95,140,0.36), rgba(10,60,95, 0.45), rgba(20,65,105, 0.88), rgba(5,90,140,0.35)); + background-color: rgba(20,80,110,0.9); + color: #fff; + letter-spacing: 0.07rem; + line-height: 18px; + margin: 3px 3px; + padding: 6px 8px; +} + +/* --- Adjust the lengths of the Performer, Movies and Tags fields while editing a scene while the scene plays --- */ +#scene-edit-details .col-sm-9 { + flex: 0 0 100%; + max-width: 100%; +} + +/* --- Cuts the name of Performers, Movies and Tags short if they go longer than the length of the field --- */ +div.react-select__control .react-select__multi-value { + max-width: 285px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + + + +.input-control, .input-control:disabled, .input-control:focus, .modal-body div.react-select__control, .modal-body .form-control { + background: rgba(15,15,20,0.36); +} + + +.scraper-table tr:nth-child(2n) { background: rgba(15,15,20,0.18);} + +.nav-pills .nav-link.active, .nav-pills .show>.nav-link { background: rgba(15,15,20,0.50);} + + +.btn-secondary:not(:disabled):not(.disabled).active, +.btn-secondary:not(:disabled):not(.disabled):active, +.show>.btn-secondary.dropdown-toggle { background: rgba(15,15,20,0.50);} + + +/* --- Background when searching for a scene in Tagger view --- */ +.search-result { background: rgba(0,0,0,0.22);} +.selected-result { background: rgba(25,120,25,0.28);} +.search-result:hover { background: rgba(12,62,75,0.35);} + + +.markdown table tr:nth-child(2n) {background: rgba(25,20,25,0.20);} +.markdown code, .markdown blockquote, .markdown pre {background: rgba(25,20,25,0.30);} + + +/* --- Changes the size of the Custom CSS field in the settings --- */ +#configuration-tabs-tabpane-interface textarea.text-input { min-width:60ex; max-width:55vw !important; min-height:50ex;} + + +div.dropdown-menu,div.react-select__menu{background-color:rgba(35,37,44,0.55);color:#f5f8fa} + +div.dropdown-menu .dropdown-item, div.dropdown-menu .react-select__option, div.react-select__menu .dropdown-item, div.react-select__menu .react-select__option +{color:#f5f8fa;background-color:rgba(35,37,44,0.55);} + +div.dropdown-menu .dropdown-item:focus,div.dropdown-menu .dropdown-item:hover,div.dropdown-menu .react-select__option--is-focused,div.react-select__menu .dropdown-item:focus,div.react-select__menu .dropdown-item:hover,div.react-select__menu .react-select__option--is-focused{background-color:rgba(24,130,195,0.85)} + + +.toast-container { + left: 74%; + top: 1rem; +} + +/* --- Settings / About adjustments --- */ +#configuration-tabs-tabpane-about .table {width:100%} +#configuration-tabs-tabpane-about .table td {padding-top:18px} + + + +/* Folder when doing selective scan or configuration */ +.input-group .form-control {color: #c9e0e7; } + + + +/* --- Overhaul of the Popoup Edit Boxes --- */ +@media (min-width: 576px) { + #setting-dialog .modal-content .modal-body textarea {min-height:350px; height:75vh !important} + .modal-dialog {max-width: 880px} + .modal-dialog .modal-content .form-group .multi-set {width:82%;margin-top:12px; flex: 0 0 82%; max-width:82%;} + .modal-dialog .modal-content .form-group .col-9 {flex: 0 0 82%;max-width: 82%;} + .modal-dialog .modal-content .col-3 { flex: 0 0 18%; max-width: 18%;} + .modal-dialog .modal-content .form-group > .form-label {margin-top:0px;flex: 0 0 18%; max-width: 18%;text-shadow: var(--std-txt-shadow);} + .modal-dialog .modal-content .form-group {display: flex; flex-wrap: wrap;} + .modal-dialog .modal-content .btn-group>.btn:not(:first-child), .btn-group>.btn-group:not(:first-child) {margin-left: 2px} + .modal-dialog .modal-content .form-label[for~="movies"], + .modal-dialog .modal-content .form-label[for~="tags"], + .modal-dialog .modal-content .form-label[for~="performers"] {margin-top:48px;} + .modal-dialog .modal-content .button-group-above {margin-left:9px} + .modal-dialog .scraper-sources.form-group h5 {margin-top:20px} + .modal-dialog .modal-content .field-options-table {width:98%} + + .modal-dialog.modal-lg .modal-content .form-group {display: inline;} +} + + +div.modal-body b, .form-label h6 {text-shadow: var(--std-txt-shadow);} + +.modal-body .btn-primary:not(:disabled):not(.disabled).active, .modal-body .btn-primary:not(:disabled):not(.disabled):active { +color: #fff; + background-color: #008558; + border-color: #0d5683; +} + +.modal-body .btn-primary { + color: #fff; + background-color: #666; + border-color: #666; +} + + +/* --- several Performer and Scene Scaping changes --- */ +.modal-content, .modal-lg, .modal-xl { + max-width: 1400px; + width:100%; +} + +.modal-header, .modal-body, .modal-footer { background: rgba(50,90,105,0.96);} +.modal-body {padding-bottom:2rem;} +.performer-create-modal {max-width:1300px;} + +.modal-body .col-form-label, .modal-body .col-6, .modal-footer, .modal-header .col-form-label {text-shadow: var(--std-txt-shadow);} + +.modal-body .col-6 strong {font-weight: normal; font-size:14px} +.modal-body .no-gutters {margin-bottom: 8px} + +.modal-body .dialog-container .col-lg-3 { + flex: 0 0 12%; + max-width: 12%; + text-shadow: var(--std-txt-shadow); +} + +.modal-body .dialog-container .offset-lg-3{margin-left:12%;} +.modal-body .dialog-container .col-lg-9{flex:0 0 88%; max-width:88%;} + + + + + +.input-group-prepend div.dropdown-menu +{ + background: rgba(50,90,105,0.94); + padding:15px; + box-shadow: 2px 2px 6px rgba(0, 0, 0, .70); +} + +.saved-filter-list .dropdown-item-container .dropdown-item { + margin-top:3px; +} +.set-as-default-button {margin-top: 8px;} + +.grid-card .card-check { top:.9rem;width: 1.5rem;} + +.btn-group>.btn-group:not(:last-child)>.btn, .btn-group>.btn:not(:last-child):not(.dropdown-toggle) + {border-left: 1px solid #394b59;} +.btn-group>.btn-group:not(:first-child), .btn-group>.btn:not(:first-child) {border-right: 1px solid #394b59;} + + +div.gallery-card.grid-card.card p div.TruncatedText, +div.movie-card.grid-card.card hr, div.movie-card.grid-card.card p {display:none} + + + +/* --- Spacing out the paginationIndex --- */ +.paginationIndex {color:#f3f3f3;margin-bottom:8px} +.paginationIndex .scenes-stats, .images-stats {margin-top:-3px; color:#9999a9} +.paginationIndex .scenes-stats:before, .images-stats:before +{ + font-size: 16px; + margin-left:18px; + margin-right:12px; + color:#ccc; + content: "-"; +} + + + + + + + + + +/* ============== SETTINGS ============== */ + +#settings-container { + padding-left:230px; + background-image: none !important; + background-color: rgba(16, 20, 25, .40) !important; + box-shadow: 2px 2px 7px rgb(0 0 0 / 75%); + border-radius: 10px; + padding-top:25px; + min-height:450px; +} + +#settings-container .card { + margin-bottom:25px; + background-image: none !important; + background-color: rgba(16, 20, 25, .00); + box-shadow: 0px 0px 0px rgb(0 0 0 / 75%); + border-radius: 0px; +} + +#settings-container .bg-dark {background-color: rgba(16, 20, 25, .12) !important;} + +.form-group>.form-group {margin-top:0.5em; margin-bottom: 0.5rem} + + +#configuration-tabs-tabpane-tasks>.form-group {margin-bottom:2rem; margin-top:1em} + +#configuration-tabs-tabpane-tasks h6 { margin-top:3.5em; font-weight:bold; margin-bottom:1em; } +#configuration-tabs-tabpane-tasks h5 { margin-top:2.0em; font-weight:bold; letter-spacing: 0.09rem; } + +.form-group h4 {margin-top:2em} + + +#parser-container.mx-auto {max-width:1400px;margin-right:auto !important} +.scene-parser-row .parser-field-title {width: 62ch} + +.job-table.card { + margin-top:-32px !important; + background-color: rgba(16, 20, 25, .20) !important; + border-radius: 10px !important; +} + + +.mx-auto {margin-right: 1% !important} +.mx-auto.card .row .col-md-2 .flex-column { position:fixed;min-height:400px} +.mx-auto.card>.row {min-height:360px} + +.loading-indicator {opacity:80%; zoom:2} + + + + +/* --- Settings - Tasks ---- */ + + +#configuration-tabs-tabpane-tasks>.form-group .card { + padding: 20px; + margin: 4px 0.40% 14px; + background-image: none; + background-color: rgba(16, 20, 25, .00); + box-shadow: none; + border-radius: 10px; +} + +#tasks-panel h1 {margin-top: 3em} +.setting-section h1, #tasks-panel h1 {font-size: 1.55rem; max-width:180px} + + +@media (min-width: 576px) and (min-height: 600px) { +#tasks-panel .tasks-panel-queue { + background: none !important; + margin-top: -2.6rem; + padding-bottom: .25rem; + padding-top: 0rem; + position: relative; + top: 0rem; + z-index: 2; +} +} + +#tasks-panel hr {border-top: 0px solid rgba(140,142,160,.38);} +#tasks-panel h1 {margin-top:1.8em;} +#tasks-panel h1 {margin-top:0.8em;} + +#configuration-tabs-tabpane-tasks {margin-top:40px} + +#configuration-tabs-tabpane-tasks .form-group:last-child .setting-section .setting div:last-child { + margin-right: 0% !important; + margin-left: 0px; + margin-top: 2px; +} + +#configuration-tabs-tabpane-tasks .setting-section .sub-heading {margin-bottom:1em} +#configuration-tabs-tabpane-tasks .setting-section .collapsible-section {margin-bottom:3em} +#configuration-tabs-tabpane-tasks #video-preview-settings button { width:250px;margin-top:22px;margin-left:-57px} +#configuration-tabs-tabpane-tasks .tasks-panel-tasks .setting-section:nth-child(3) {margin-top:5em} + +.tasks-panel-tasks .setting a { color: #ccccd3;} + + + + + + +@media (min-width: 1000px) { + #settings-container .card {margin-top:-43px; margin-left:255px} +} + + + +#settings-container .col-form-label { + padding-top: calc(.55rem); + padding-bottom: calc(.55rem); +} + +.setting-section .setting-group>.setting:not(:first-child), .setting-section .setting-group .collapsible-section .setting { + margin-left: 4rem; +} + +.setting-section .setting h3 { + font-size: 1.4rem; + margin:0.6em 0 0.4em; +} + +.setting-section:not(:first-child) {margin-top: 1em} +.setting-section .setting-group>.setting:not(:first-child).sub-setting, .setting-section .setting-group .collapsible-section .setting.sub-setting { padding-left: 3rem;} + + + +@media (min-width: 1200px) { + .offset-xl-2 { + max-width:1250px; + margin-left:15%; + margin-right:auto; + } + + #settings-container .tab-content, .mx-auto { max-width: none} +} + +.setting-section .setting:not(:last-child) { border-bottom: 0px solid #000 } + + + + + + +/* --- Checkboxes instead of Switches ---*/ + +.custom-switch {padding-left:2.25rem} +.custom-control { + min-height: 1.5rem; + padding-left: 0.5rem; + margin-right:1em; +} +.custom-control-input:checked~.custom-control-label:before { + color: rgb(0 0 0 / 0%); + border-color: rgb(0 0 0 / 0%); + background-color: rgb(0 0 0 / 0%); +} +.custom-switch .custom-control-label:before { + pointer-events: auto; + border-radius: 0; +} +.custom-switch .custom-control-input:checked~.custom-control-label:after { + background-color: blue; + transform: auto; +} +.custom-switch .custom-control-label:after { + top: auto; + left: auto; + width: auto; + height: auto; + background-color: blue; + border-radius: 0; + transform: none; + transition: none; +} + +.custom-control-label:before {display:none} +.custom-control-input { + position: absolute; + top:2px; + left: 0; + z-index: -1; + width: 1.2rem; + height: 1.35rem; + opacity: 1; + background-color:#10659a; + color:#10659a; +} + + + + +.setting-section {margin-bottom:0.8em} +.setting-section .setting-group>.setting:not(:first-child), .setting-section .setting-group .collapsible-section .setting { + padding-bottom: 3px; + padding-top: 4px; + margin-right: 0rem; +} +.setting-section .sub-heading { + font-size:.9rem; + margin-top:0.5rem; + margin-bottom:3rem; +} + + +/* --- Settings - Interface ---- */ + + +@media (min-width: 768px) { +.offset-md-3 {margin-left: 1%;} +#settings-menu-container {margin-left:1%; z-index:9; width:200px; padding-top:25px;} + + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div:nth-child(2) { width:64%;min-width:300px;padding-top:6px} + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div .sub-heading {margin-top:-28px; margin-left:255px;width:700px} + #language .input-control { width:250px} + + #configuration-tabs-tabpane-interface .setting-section .setting-group>.setting:not(:first-child) {margin-left:275px} + #configuration-tabs-tabpane-interface .setting-section .setting-group>.setting:nth-child(2) {margin-top: -40px} + #configuration-tabs-tabpane-interface .setting-section:first-child h3 { font-size: 1.55rem;} +} + +@media (min-width: 1200px) { + .offset-md-3 {margin-left: 14%;margin-right:2%} + .setting-section h1, #tasks-panel h1 { max-width:220px;} + #settings-menu-container { + padding-top:25px; + margin-left:14% !important; + z-index:9; + width:205px; + } +} + +@media (max-width: 768px) { + .offset-md-3 {margin-left: 1%;} + #settings-menu-container {margin-left:1%; z-index:9;width:180px; padding-top:25px;} + #settings-container { padding-left: 180px;} + .setting-section h1, #tasks-panel h1 { max-width:300px;} +} + +@media (max-width: 576px) { + .offset-sm-3 {margin-left: 1%;} + #settings-menu-container {margin-left:1%;z-index:9;width:160px; padding-top:25px;} + #settings-container {padding-left: 10px;} +} + +@media (max-width: 1004px) { + .setting-section h1, #tasks-panel h1 { max-width:400px;} + .job-table.card {margin-top:2px !important;} +} + + + + +.markdown table tr:nth-child(2n), +.modal-body .nav-link:hover, +#settings-menu-container .nav-link:hover {background-color: rgba(10, 20, 20, .15)} + + + +@media (min-width: 1000px) { + #settings-container #configuration-tabs-tabpane-interface .setting-section > .setting { padding: 15px 0px;} + #settings-container #configuration-tabs-tabpane-interface .setting-section .setting-group .setting>div:first-item{margin-left: 4% !important;} + + #settings-container #stash-table {margin-top:25px} + #configuration-tabs-tabpane-interface .setting-section:first-child .card { margin-top: -5px; margin-left: -1%} + + #language .input-control { width:250px} + + #configuration-tabs-tabpane-interface .setting-section:first-child .card > .setting div:nth-child(1) { width:255px} + + + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div:nth-child(2) { width:68%;padding-top:6px} + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div .sub-heading {margin-top:-28px; margin-left:255px;width:700px} + + #configuration-tabs-tabpane-interface #language {margin-bottom:15px} + #configuration-tabs-tabpane-library #stashes .sub-heading {margin-top:-26px; margin-left:235px;width:700px} + +} + + + +#configuration-tabs-tabpane-metadata-providers .setting, +#configuration-tabs-tabpane-security .setting, +#configuration-tabs-tabpane-tasks .setting, +#configuration-tabs-tabpane-system .setting-section .setting, +#settings-dlna .setting-section .setting, +#configuration-tabs-tabpane-interface .setting-section .setting {padding-top:0; padding-bottom:0} + + +#configuration-tabs-tabpane-interface .setting-section:nth-child(1) h1 {display:none} + +#configuration-tabs-tabpane-interface .setting-section .setting-group>.setting:not(:first-child) h3 { + font-size: 1rem; + margin-left:2em; +} + +#configuration-tabs-tabpane-interface .setting-section .setting-group .setting>div:last-child { + margin-right: 95% !important; + margin-left:0px; + margin-top:-32px; +} + +.setting-section .setting>div:first-child {max-width:415px} + +#configuration-tabs-tabpane-interface .setting-section .setting>div:last-child { + min-width: 20px; + text-align: left; + width:38%; + +} + +#configuration-tabs-tabpane-interface h3 {font-size:1.25em} + +#wall-preview .input-control {width:160px} + +.setting-section .setting-group>.setting:not(:first-child), .setting-section .setting-group .collapsible-section .setting { + padding-top: 0px; + padding-bottom: 0px; + margin-right: 0rem; + line-height:100%; + margin-top:-3px; + margin-bottom:-4px; +} + +#configuration-tabs-tabpane-interface .setting-section:nth-child(7) .setting {margin-left:15px !important} +#configuration-tabs-tabpane-interface .setting-section:nth-child(7) .setting:nth-child(1) {margin-left: 0px !important;} + + +#settings-dlna h5 {margin-bottom:70px} +#settings-dlna .form-group h5{margin-left:255px;margin-top:-30px} + +#configuration-tabs-tabpane-metadata-providers #stash-boxes .sub-heading {margin-top:-28px; margin-left:235px;width:700px;font-size:14px} + +.scraper-table tr:nth-child(2n) {background-color: rgba(16, 20, 25, .12)} + + + +/* --- Library ---*/ +.stash-row .col-md-2 {padding-left:4%} +#configuration-tabs-tabpane-library .setting-section .setting {padding:0} + + + +#configuration-tabs-tabpane-security .setting-section, +#configuration-tabs-tabpane-tasks .setting-section, +#configuration-tabs-tabpane-tasks .setting-group{max-width:915px} + +#configuration-tabs-tabpane-logs .setting-section, +#configuration-tabs-tabpane-metadata-providers .setting-section, +#configuration-tabs-tabpane-services .setting-section, +#configuration-tabs-tabpane-system .setting-section, +#configuration-tabs-tabpane-library .setting-section:not(:first-child), +#configuration-tabs-tabpane-interface .setting-section {max-width:810px} + +#configuration-tabs-tabpane-security .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-metadata-providers .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-services .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-system .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-library .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-interface .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-tasks .setting-section .setting>div:last-child { + min-width: 20px; + text-align: right; + width:auto; + +} + +#configuration-tabs-tabpane-tasks .setting-section .collapsible-section .setting div:last-child { + margin-right: 95% !important; + margin-left: -12px; + margin-top: -15px; +} + + + +#configuration-tabs-tabpane-system .setting-section .sub-heading {margin-bottom: 1.2rem} + + diff --git a/themes/Theme-Pulsar/Theme-Pulsar.yml b/themes/Theme-Pulsar/Theme-Pulsar.yml new file mode 100644 index 00000000..fc9ae174 --- /dev/null +++ b/themes/Theme-Pulsar/Theme-Pulsar.yml @@ -0,0 +1,6 @@ +name: Theme - Pulsar +description: Plex Theme for Stash by Fonzie 2020-21 +version: 1.8.1 +ui: + css: + - Theme-Pulsar.css diff --git a/themes/Theme-PulsarLight/Theme-PulsarLight.css b/themes/Theme-PulsarLight/Theme-PulsarLight.css new file mode 100644 index 00000000..baa47ff2 --- /dev/null +++ b/themes/Theme-PulsarLight/Theme-PulsarLight.css @@ -0,0 +1,1830 @@ +/* Light Pulsar Theme - Fonzie 2021 v0.3.1 */ +/* ---------------------------------------------------- */ +/* --------- Updated to Stash version 0.12.0 ---------- */ + +/* + Bug Fixes: Overlap of Help & Ssettings" buttons in the navbar, as well + as the Identify task + + Complete overhaul of the Settings page + + Bug Fix: Background-color in the navigation bar + + Adjustments to version 0.10.0 which includes moving the movie-, image- + and gallery-counter to the bottom of the performer image when you hover + over the card, and increasing the size of the star rating in the highest + zoom level. +*/ + + +/* ===================== General ===================== */ + +body { + background-image:url("https://i.imgur.com/UwICmXP.jpg"); /* Flower */ +/* background-image:url("https://i.imgur.com/zqt3MFY.jpg"); /* Green Leaves */ +/* background-image:url("https://i.imgur.com/vCotzwB.jpg"); /* White Desert */ +/* background-image:url("https://i.imgur.com/Lverfqy.jpg"); /* Tropic Beach */ +/* background-image:url("https://i.imgur.com/4jrpuyR.jpg"); /* White Blue Waves */ +/* background-image:url("https://i.imgur.com/KUtfQzs.jpg"); /* Bright Lights */ + + width: 100%; + height: 100%; + padding: 0 0 0; + background-size: cover; + background-repeat: no-repeat; + background-color:#127aa5; + background-attachment: fixed; + background-position: center; + color: #f9fbfd; + color:#000; +} + +h1, h2, h3{ color:#fff;} + +:root { + --HeaderFont: Helvetica, "Helvetica Neue", "The Sans", "Segoe UI"; + --std-txt-shadow: 2px 2px 1px #000; + --light-txt-shadow: 1px 1px 3px #555; + --white: #ffffff; + --stars: url("https://i.imgur.com/YM1nCqo.png"); + --fourTwo: 0.35; +} + + +/* --- The Home button in the top left corner of each page. Remove the last 3 lines if you don't like the logo --- */ +button.minimal.brand-link.d-none.d-md-inline-block.btn.btn-primary, +button.minimal.brand-link.d-inline-block.btn.btn-primary { + text-transform: uppercase; + font-weight: bold; + margin-left:1px; + background-image:url("./favicon.ico"); + padding-left:40px; + background-repeat: no-repeat; +} + +/* --- Makes the background of the Navigation Bar at the Top half-transparent --- */ +nav.bg-dark {background: rgba(100, 200, 250, var(--fourTwo))!important;color:#000} +.bg-dark {background:none !important;background-color:none !Important} +.form-group .bg-dark {background: rgba(10, 20, 25, 0.20)!important;} + +.navbar-buttons.navbar-nav a.nav-utility {margin-right:9px} + + +/* --- The space between the Navigation Bar and the rest of the page --- */ +.main { margin-top:18px } +.top-nav { padding: .13rem 1rem; } + + +/* --- Changes how the Bar at the top of the page behaves --- */ +.fixed-bottom, .fixed-top { position: relative !important; top:0px !important} + + +/* The pagination at the top and at the bottom of the Scenes/Performer/Images/... pages; +transparent background, rounded corners, etc. */ +.filter-container, .operation-container { + background-color: rgba(100, 150, 160, .35); + box-shadow: none; + margin-top: 6px; + border-radius: 5px; + padding: 5px; +} + + +/* --- Changes the space between the button in the top right corner of the page --- */ +.order-2 button { margin-left: 4px } + +/* --- Space between the items in the Nav bar --- */ +.nav-link > .minimal { margin: 0px;} + + +/* Each item on the Scenes/Performers/Tags/... pages */ +.card { + padding: 20px; + margin: 4px 0.5% 14px; + /* --- Adds a glowing shimmer effect --- */ + background-image: linear-gradient(130deg, rgba(60, 70, 85,0.21), rgba(150, 155, 160,0.30), rgba(35, 40, 45,0.20), rgba(160, 160, 165,0.21), rgba(70, 80, 85,0.27)); + background-color: rgba(106, 120, 125, .25); + box-shadow: 2px 2px 6px rgba(0, 0, 0, .55); + /* --- Increases the rounded borders of each item on the Scenes/Performers/... pages for 6px in 10px --- */ + border-radius: 10px; +} + +/* --- Removes the glowing shimmer effect on the start page & the settings for readability purpose --- */ +.mx-auto.card, .changelog-version.card { + background-image: none !important; + background-color: rgba(16, 20, 25, .40) !important; +} + +/* --- Color that is used within .card secments --- */ +.text-muted { color: #eee !important; text-shadow: 1px 1px 2px #000;} + + +.bg-secondary { + background: none; + background-color: rgba(10, 25, 30, .3) !important; +} + +.text-white { color: #333 } +.border-secondary { border-color: #2f3335 } + +.btn-secondary.filter-item.col-1.d-none.d-sm-inline.form-control { + background-color: rgba(0, 0, 0, .08); +} + +/* --- Changes the color and the background of the buttons and elements in the toolbar (Search, Sort by, # of Items, etc.) --- */ +.btn-secondary { + color: #eef; + background-color: rgba(45, 45, 45, .28); + border-color: #3c3f45; +} +.btn-toolbar .btn-secondary { color: #404049; background-color: rgba(130, 130, 140, .28);} + + +a { color: hsla(0, 10%, 10%, .85);} + + + +/* --- Changes the color of the active page in the Navgation Bar --- */ +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + background-color: rgba(15, 150, 205, .6); + border-color: #fff; +} + +/* --- No border of the active element in the Navgation Bar --- */ +.btn-primary.focus, +.btn-primary:focus, +.btn-primary:not(:disabled):not(.disabled).active:focus, +.btn-primary:not(:disabled):not(.disabled):active:focus, +.show>.btn-primary.dropdown-toggle:focus {box-shadow: none;} + +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show>.btn-primary.dropdown-toggle { + color: #fff; + border-color: #eee; + text-shadow: 1px 1px 2px #333; + } + +.container-fluid,.container-lg,.container-md,.container-sm,.container-xl { + width: 100%; + margin-right: 0px; + margin-left: 0px; +} + + + + + +/* ===================== Performer ========================= */ + + +/* --- 0.90 - Section moves Movie-, Image- & Gallery-Count to the bottom of the performer image when hovered over --- */ +.performer-card .card-popovers .movie-count, +.performer-card .card-popovers .image-count, +.performer-card .card-popovers .gallery-count +{ + z-index:300; + position:absolute; + top:-270%; + opacity:0.0; +} + +/* --- Highlight the bottom of the performer card when hovered over --- */ +.performer-card.grid-card:hover { + background-image: linear-gradient(130deg, rgba(50, 60, 75,0.25), rgba(150, 155, 160,0.32), rgba(35, 40, 45,0.26), rgba(160, 160, 165,0.27), rgba(50, 60, 65,0.37)); + background-color: rgba(102, 112, 120, .25); +} + +/* --- When hovered over blend them in ---*/ +.performer-card.grid-card:hover .card-popovers .movie-count, +.performer-card.grid-card:hover .card-popovers .image-count, +.performer-card.grid-card:hover .card-popovers .gallery-count {opacity: 1.0;transition: opacity .7s;} + +/* --- 3 items gets a shadow ---*/ +.performer-card .card-section .movie-count span, +.performer-card .card-section .movie-count button.minimal, +.performer-card .card-section .image-count span, +.performer-card .card-section .image-count button.minimal, +.performer-card .card-section .gallery-count span, +.performer-card .card-section .gallery-count button.minimal +{text-shadow: 2px 2px 1px #000, 1px 1px 1px #000, 4px 4px 5px #333, 9px 0px 5px #333, -3px 2px 4px #333, -7px 0px 5px #333, -1px -6px 5px #333, 3px -2px 6px #444;} + +/* --- Positioning of the 3 items ---*/ +.performer-card .card-popovers .movie-count {left:0.2%;} +.performer-card .card-popovers .image-count {left:32.8%} +.performer-card .card-popovers .gallery-count {right:1.3%} + +.performer-card .movie-count a.minimal:hover:not(:disabled), .performer-card .movie-count button.minimal:hover:not(:disabled), +.performer-card .image-count a.minimal:hover:not(:disabled), .performer-card .image-count button.minimal:hover:not(:disabled), +.performer-card .gallery-count a.minimal:hover:not(:disabled), .performer-card .gallery-count button.minimal:hover:not(:disabled) +{ + background-color:rgba(20,80,110,0.92); + color:#fff; +} + +/* --- Affects the Scenes- and Tags-Counter --- */ +a.minimal:hover:not(:disabled), button.minimal:hover:not(:disabled) {background: rgba(138,155,168,.45);color:#fff;} +div.performer-card div.card-popovers +{ + margin-bottom:-3px; + margin-left:3%; + margin-top:-4px; + margin-right: -1px; + justify-content: flex-end; + text-align:right; +} + +div.card-section hr {display:none} + + + +/* --- Changes the width of the Performer Card from 280px to a dynamic system and therefore the size of the image --- */ +/* --- In Full screen HD 1920x1080 you now see 8 performers per row instead of 6 --- */ +/*.performer-card-image, .performer-card, .card-image { min-width: 160px; width: calc(108px + 10.625vw / 2); max-width: 230px } */ +/*.performer-card-image, .performer-card, .card-image { min-width: 160px; width: calc(108px + 19vw / 3.6);width:212px; max-width: 230px } */ +.performer-card-image, .performer-card, .card-image { min-width: 160px; width: calc(100px + 11.2vw / 1.92);max-width: 230px } + + +/* --- Changes the height of the Performer Card to keep the 2x3 picture ratio --- */ +/*.performer-card-image, .justify-content-center .card-image { min-height:240px; height: calc((108px + 10.625vw / 2) * 1.5); max-height: 345px} */ +.performer-card-image, .justify-content-center .card-image { min-height:240px; height: calc((112px + 19vw / 3.6) * 1.5); max-height: 345px;} +.performer-card-image, .justify-content-center .card-image { min-height:240px; height: calc((100px + 11.2vw / 1.92) * 1.5); max-height: 345px;} + +@media (max-width: 575px), (min-width: 1200px) { +.scene-performers .performer-card-image { height: auto; } +.scene-performers .performer-card { width: auto; } +} + + +/* --- Fixes an issue of the card when watching a scene --- */ +.image-section { display: cover;} + +/* --- The name of the Performer. Different font, less space to the left & to the top, Text-Shadow --- */ +.text-truncate, .card.performer-card .TruncatedText { + margin-left:-1.5%; + margin-top: -2px; + width: 120%; + font-family: var(--HeaderFont); + font-size: 112%; + line-height:130%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} + +/* --- Makes the Performer Name smaller when the screen is too small --- */ +@media (max-width: 1200px) { .card.performer-card .TruncatedText { font-size: 104%; } } + + + +/* --- Moves the Flag icon from the right side of the card to the left and makes the Flag image a little bit bigger --- */ +.performer-card .flag-icon { + height: 2rem; + left: 0.6rem; + bottom: 0.10rem; + width: 28px; +} + +/* --- Age and # of Scenes move from the left side to the right of the card --- */ +/* --- Also makes sure that when playing a scenes "XX years old in this scene" doesn't overlap with the star rating --- */ +.performer-card .text-muted {text-align:right;margin-top:-2px;margin-bottom:1px;width:46%;margin-left:54%} + + + +/* --- "removes" the term 'old.' from "xx years old." when the resolution gets to small --- */ +@media (max-width: 1520px) { +div.card.performer-card .text-muted {text-align:right;margin-top:-2px;margin-bottom:1px;margin-right:-33px;max-height:20px;overflow:hidden;} +} + +/* --- To prevent overlapping in the performer card when watching a scene --- */ +@media (max-width: 2000px) { +.tab-content div.card.performer-card .text-muted {margin-top:22px;margin-right:-3px} +.tab-content .performer-card.card .rating-1, +.tab-content .performer-card.card .rating-2, +.tab-content .performer-card.card .rating-3, +.tab-content .performer-card.card .rating-4, +.tab-content .performer-card.card .rating-5 {bottom: 53px !important;} +} + + +/* --- Text Shadow for the "Stars in x scenes" link --- */ +div.card.performer-card div.text-muted a {text-shadow: 1px 2px 2px #333} + +/* --- Minimum height for the section in case Age is missing and elements would overlap --- */ +.performer-card .card-section {min-height:82px} + +/* --- Makes the card section (Name, Age, Flag, # of scenes) more compact --- */ +.card-section { margin-bottom: -8px !important; padding: .5rem 0.7rem 0 !important;} +.card-section span {margin-bottom:3px} +@media (max-width: 1500px) { .card-section span {font-size:11px} } + +div.card-section hr {display:none} + + + + +/* --- Changes regarding the Favorite <3 --- */ +.performer-card .favorite { + color: #f33; + -webkit-filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, .95)); + filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, .95)); + right: 3px; + top: 5px; +} + + + +/* --- Turns the Rating Banner in the top left corner into a Star Rating under the performer name --- */ +.performer-card.card .rating-1, .performer-card.card .rating-2, .performer-card.card .rating-3, +.performer-card.card .rating-4, .performer-card.card .rating-5 +{ + background:none; + background-size: 97px 18px; + background-image:var(--stars); + -webkit-transform:rotate(0deg); + transform:rotate(0deg); + padding:0; + padding-bottom:1px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, .00); + left:6px; + width:97px; + height:18px; + text-align:left; + position:absolute; + top:auto; + bottom: 34px; + font-size:0.001rem; +} + +/* --- Display only X amount of stars -- */ +div.performer-card.card .rating-banner.rating-1 {width:20px} +div.performer-card.card .rating-banner.rating-2 {width:39px} +div.performer-card.card .rating-banner.rating-3 {width:59px} +div.performer-card.card .rating-banner.rating-4 {width:78px} +div.performer-card.card .rating-banner.rating-5 {width:97px} + + +.performer-card .btn {padding: .34rem .013rem} +.performer-card .fa-icon {margin: 0 2px} +.performer-card .card-popovers .fa-icon {margin-right: 3px} +.performer-card .svg-inline--fa.fa-w-18, .performer-card .svg-inline--fa.fa-w-16 {height: 0.88em} +.performer-card .favorite .svg-inline--fa.fa-w-16 {height:1.5rem} + + +.performer-card .card-popovers .btn-primary { + margin: 0 2px 0 11px; +} + + + +/* --- PerformerTagger Changes --- */ + +.PerformerTagger-performer { + background-image: linear-gradient(130deg, rgba(50, 60, 75,0.25), rgba(150, 155, 160,0.32), rgba(35, 40, 45,0.26), rgba(160, 160, 165,0.27), rgba(50, 60, 65,0.37)); + background-color: rgba(16, 20, 25, .23); + box-shadow: 2px 2px 6px rgba(0, 0, 0, .70); + border-radius: 8px; + margin: 1.1%; + } + +.tagger-container .input-group-text {background:none;border:0;margin-right:5px;padding-left:0} +.PerformerTagger-details { margin-left: 1.25rem; width:23.5rem;} + +.tagger-container .btn-link{text-shadow: 1px 2px 3px #000;} +.tagger-container, .PerformerTagger { max-width: 1850px;} + +.PerformerTagger-header h2 { + font-family: Helvetica, "Helvetica Neue", "Segoe UI" !important; + font-size: 2rem; + line-height:130%; + font-weight:bold; + text-shadow: 2px 2px 1px #000 !important +} + +.PerformerTagger-thumb {height: 145px;} +.PerformerTagger-performer-search button.col-6 {width:300px !important; flex-basis:100%;flex: 0 0 100%; + max-width: 100%;} +.PerformerTagger-performer .performer-card {height:252px !important;} + +.modal-backdrop { background-color: rgba(16, 20, 25, .25);} +.modal-backdrop.show { opacity: 0.1; } + +.performer-create-modal { max-width: 1300px; font-family: Helvetica, "Helvetica Neue", "Segoe UI" !important; } +.performer-create-modal .image-selection .performer-image { height: 95%; } +.performer-create-modal .image-selection {height: 485px;} + +.performer-create-modal .no-gutters .TruncatedText { + font-family: var(--HeaderFont); + font-size: 115%; + padding-top:2px; + line-height:120%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} +.performer-create-modal-field strong {margin-left: 6px} +.modal-footer {border-top: 0} + + + + + + +/* ========================= Performer Page ================================= */ +/* === The page that you see when you click on the picture of a Performer === */ + +/* --- The picture of the Performer on the left. 3D effect thanks to background shadows and more rounded corners --- */ +#performer-page .performer-image-container .performer +{ + background-color: rgba(0, 0, 0, .48); + box-shadow: 6px 6px 11px rgba(0, 10, 10, .62); + border-radius: 14px !important; +} + +/* --- Without this the shadow at the bottom from the previous Selector will not be correctly displayed --- */ +.performer-image-container {padding-bottom: 11px} + + +/* --- The following 15 Selectors change the way the details box is displayed --- */ +#performer-details-tabpane-details .text-input, #performer-details-tabpane-details .text-input:disabled, +#performer-details-tabpane-details .text-input[readonly] {background-color: rgba(200,220,246,0.0);} +#performer-details-tabpane-details a { text-shadow: var(--light-txt-shadow)} + +.text-input, input.form-control-plaintext { background-color:none;} +#performer-details .input-control, .text-input {box-shadow: none;} + +div.react-select__control, #performer-details-tabpane-details { + background-color: rgba(225,230,250,0.36); + border:1px solid #999; + max-width:1000px; + box-shadow: 6px 6px 12px rgba(0, 10, 10, .22); +} +#performer-details-tabpane-details {border-radius: 10px} +#performer-details-tabpane-edit {max-width:1000px} + +div.react-select__control .css-12jo7m5 {text-shadow: none; } + +@media (min-width: 1200px) { + #performer-details-tabpane-details td { padding: 9px; } + table#performer-details tbody tr td:nth-child(1), td:first-child {padding-left: 22px; width: 185px;} +} +@media (max-width: 1200px) { + table#performer-details tbody tr td:nth-child(1), td:first-child {padding-left: 11px; } + #performer-page .performer-head { margin-bottom: 1rem; } + #performer-page { margin: 0 -6px 0 -15px; } +} +#performer-details-tabpane-details tr:nth-child(odd) { background-color: rgba(16,22,26,0.1); } +table#performer-details {color:#FFF; text-shadow: 1px 1px 1px #000;} + + + +/* --- Changes the way the name of the performer is displayed --- */ +.performer-head h2 {font-family: var(--HeaderFont); font-weight:bold; text-shadow: 2px 2px 2px #111 } + +/* --- Leave some space between the name and the Fav/Link/Twitter/IG icons --- */ +#performer-page .performer-head .name-icons {margin-left: 22px} + +/* --- Highlighter for active Details/Scenes/Images/Edit/Operations --- */ +.nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { + background-color: rgba(135,160,165,0.5); +} + +/* --- Changes the display of Performer Details Tab in the 0.9 version are arranged --- */ +#performer-details-tabpane-details dl.row, dl.details-list dt, dl.details-list dd{ margin:0 0px;padding: 8px 10px 9px 14px} +#performer-details-tabpane-details dl.row:nth-child(odd), +#performer-details-tabpane-details dl.details-list dt:nth-of-type(odd), +#performer-details-tabpane-details dl.details-list dd:nth-of-type(odd) { background-color: rgba(16,22,26,0.1);} +#performer-details-tabpane-details dt.col-xl-2, +#performer-details-tabpane-details dl.details-list dt { text-shadow: var(--light-txt-shadow); font-weight: normal;} +#performer-details-tabpane-details ul.pl-0 {margin-bottom: 0rem;} +#performer-details-tabpane-details dl.details-list { grid-column-gap: 0} + + +/* --- Resets the fields in Performer Edit Tab in the 0.5 developmental version back to way it was in the 0.5 version --- */ +#performer-edit {margin:0 0 0 10px} +#performer-edit .col-sm-auto, #performer-edit .col-sm-6, #performer-edit .col-md-auto, #performer-edit .col-lg-6, #performer-edit .col-sm-4, #performer-edit .col-sm-8 { width: 100%;max-width: 100%; flex: 0 0 100%; } +#performer-edit .col-sm-auto div, #performer-edit label.form-label { float:left; width:17%;} +#performer-edit .col-sm-auto div, #performer-edit label.form-label { font-weight:normal; color: #FFF; text-shadow: var(--std-txt-shadow); } + +#performer-edit select.form-control, #performer-edit .input-group, #performer-edit .text-input.form-control { float:right; width:83%; } +#performer-edit .form-group, .col-12 button.mr-2 {margin-bottom: 0.35rem} +#performer-edit .mt-3 label.form-label { float:none; width:auto; } + +#performer-edit select.form-control, #performer-edit .input-group, #performer-edit .text-input.form-control {width: 100%;} +#performer-edit textarea.text-input {min-height: 9ex;} + +#performer-edit .form-group:nth-child(17) .text-input.form-control {width:85%;} + +@media (max-width: 750px) { +#performer-edit .col-sm-auto div, #performer-edit label.form-label { float:left; width:22%;} +#performer-edit select.form-control, #performer-edit .input-group, #performer-edit .text-input.form-control { float:right; width:78%; } +} + +@media (max-width: 500px) { +#performer-edit .col-sm-auto div, #performer-edit label.form-label { float:left; width:60%;} +#performer-edit li.mb-1, +#performer-edit select.form-control, +#performer-edit .input-group, #performer-edit .text-input.form-control { float:left; width:89%; } +} + +#performer-edit .form-group .mr-2 {margin-right:0!important} + + + + + +/* ======================= Scenes ======================== */ + + +/* --- Remove the comments if you don't want to see the Description Text of the scenes --- */ +/* .card-section p {display:none} */ + + +/* --- Remove the comments if you don't want to see the Resolution of the Video (480p, 540p, 720p, 1080p) --- */ +/* .overlay-resolution {display:none} */ + + + +/* --- The name of the Scene. Different font, less space to the left and to the top, Text-Shadow --- */ +h5.card-section-title, .scene-tabs .scene-header { + font-family: var(--HeaderFont); + font-size: 1.25rem; + font-weight:bold; + line-height:132%; + text-shadow: var(--std-txt-shadow); +} +.scene-tabs .scene-header { font-size: 24px; margin-bottom:25px } + + +#TruncatedText .tooltip-inner {width:365px; max-width:365px} +.tooltip-inner { font-family: var(--HeaderFont); + background-color: rgba(16, 20, 25, .99); + box-shadow: 2px 2px 6px rgba(0, 0, 0, .55); + font-weight:bold;font-size:14px;} + +/* --- Removes the horizontal line that separates the date/description text from the Tags/Performer/etc. icons --- */ +.scene-card.card hr, .scene-card.card>hr{ border-top: 0px solid rgba(0,0,0,.1); } + + +/* --- Changes regarding the Scene Logo --- */ +.scene-studio-overlay { + opacity: .80; + top: -3px; + right: 2px; +} + +/* --- The Resolution and the Time/Length of the video in the bottom right corner to make it easier to read --- */ +.scene-specs-overlay { + font-family: Arial, Verdana,"Segoe UI" !important; + bottom:1px; + color: #FFF; + font-weight: bold; + letter-spacing: 0.035rem; + text-shadow: 2px 2px 1px #000, 4px 4px 5px #444, 7px 0px 5px #444, -3px 2px 5px #444, -5px 0px 5px #444, -1px -4px 5px #444, 3px -2px 6px #444; +} +.overlay-resolution {color:#eee;} + +/* --- Changes the spaces between the items on the Scenes page --- */ +.zoom-0 {margin: 4px 0.50% 10px; !important } + + +.scene-card-link {height:195px; overflow:hidden;} + + +/* --- Tightens the space between the Tags-, Performer-, O-Counter-, Gallery- and Movie-Icons --- */ +.btn-primary { margin:0 -3px 0 -9px} + +/* --- Moves the Tags-, Performer-, O-Counter-, Gallery- and Movie-Icon from below the description to the bottom right corner of the card --- */ +.scene-popovers, .card-popovers { + min-width:0; + margin-bottom: 3px; + margin-top:-40px; + justify-content: flex-end; +} + +/* --- Adds an invisible dot after the description text, Also leaves ~80 pixels space to enforce a line break, +so it leaves some space in the bottom right corner of the card for the icons in the Selector above --- */ +.card-section p:after +{ + font-size: 1px; + color: rgba(0,0,0, .01); + padding-right: 3.2vw; + margin-right: 2.8vw; + content: " "; +} + + + + +/* -- The whole section replaces the ratings banner with a star rating in the bottom left corner --- */ +.scene-card.card .rating-1 {width:22px} +.scene-card.card .rating-2 {width:43px} +.scene-card.card .rating-3 {width:65px} +.scene-card.card .rating-4 {width:86px} +.scene-card.card .rating-5 {background:none; width:108px} +.rating-1, .rating-2, .rating-3, .rating-4, .scene-card.card .rating-5 { + background:none; + background-image:var(--stars); + height:20px; + background-size: 108px 20px; +} + +.scene-card.card .rating-banner { + padding:0; + left:5px; + top:89%; + background-position: left; + font-size: .01rem; + -webkit-transform: rotate(0deg); + transform: rotate(0deg); +} + + +.zoom-0.card .scene-card.card .rating-banner {top: 81%} +.zoom-2.card .scene-card.card .rating-banner {top: 91%} +.zoom-3.card .scene-card.card .rating-banner {top: 92%} + + + +/* --- Improves how the Preview Videos in the Wall View are displayed --- */ +.wall-item-container {width: 100%; background-color: rgba(0, 0, 0, .10); overflow:hidden } +.wall-item-media { height:100%; background-color: rgba(0, 0, 0, .0);overflow:hidden } +.wall-item-anchor { width: 102%; overflow:hidden } +.wall-item-text {margin-bottom:0px; color: #111; text-shadow: 1px 1px 1px #fff } + + +.scene-popovers .fa-icon {margin-right: 2px;} + +/* --- Changes the Organized Button color when watching a video. Organized = Green, Not Organized = Red --- */ +.organized-button.not-organized { color: rgba(207,10,20,.8); } +.organized-button.organized { color: #06e62e;} + + +/* --- Changes the font in the File Info section --- */ +div.scene-file-info .TruncatedText, div.scene-file-info .text-truncate { + margin-left:-1.5%; + margin-top: -1px; + width: 120%; + font-family: var(--HeaderFont); + font-size: 110%; + line-height:120%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} + + +#scene-edit-details .pl-0 { + padding-left: 10px!important; +} + + +/* Zoom 0 */ +.zoom-0 { width:290px} +.zoom-0 .video-section {height:181px;} +.zoom-0 .scene-card-preview-image, .zoom-0 .scene-card-preview { height:195px; } +.zoom-0 .scene-card-preview, .zoom-0 .scene-card-preview-video, .zoom-0 .scene-card-video { + width: 290px; + min-height:181px; + max-height: 200px; +} + +/* Zoom 1 */ +.zoom-1 { min-width: 300px; width: calc(234px + 24vw /3.84);max-width: 430px} +/* Improves the way the scene picture is displayed when the resolution isn't 16:9 (e.g. 4:3) --- */ +.zoom-1 .video-section {height:calc((233px + 24vw / 3.84)/1.65);max-height: 258px} +.zoom-1 .scene-card-preview-image, .zoom-1 .scene-card-preview { height:100%; max-height: 260px} + +/* +.zoom-1 .scene-card-preview-image, .zoom-1 .scene-card-preview { height:calc((237px + 24vw / 3.84)/1.65); max-height: 260px} +*/ +.zoom-1 .scene-card-preview, .zoom-1 .scene-card-preview-video, .zoom-1 .scene-card-video { + min-width: 300px; width: calc(228px + 17vw / 1.92);max-width: 470px; + height:calc((234px + 26vw / 3.84)/1.65); + max-height: 265px; +} + +/* Zoom 2 */ +.zoom-2 { min-width: 350px; width: calc(310px + 26vw / 3.84);max-width: 495px} +.zoom-2 .video-section {height:calc((310px + 26vw / 3.84) /1.65);max-height:295px} +.zoom-2 .scene-card-preview-image, .zoom-2 .scene-card-preview { height:calc((313px + 26vw / 3.84) /1.65); max-height:292px} + +.zoom-2 .scene-card-preview, .zoom-2 .scene-card-preview-video, .zoom-2 .scene-card-video { + min-width: 350px; width: calc(330px + 28vw / 3.84);max-width: 530px; + height:calc((310px + 28vw / 3.84) /1.65); + max-height: 298px; +} + + +/* Zoom 3 */ +.zoom-3 { min-width: 400px; width: calc(530px + 18vw / 5.76);max-width: 590px} +.zoom-3 .video-section {height:375px;} +.zoom-3 .scene-card-preview-image, .zoom-3 .scene-card-preview { height:395px; } +.zoom-3 .scene-card-preview, .zoom-3 .scene-card-preview-video, .zoom-3 .scene-card-video { + width: 600px; + min-height:385px; + max-height: 400px; +} + + +.zoom-0 .video-section, .zoom-1 .video-section, .zoom-2 .video-section, .zoom-3 .video-section +{object-fit: cover !important;overflow:hidden;} + +.zoom-0 .scene-card-preview, .zoom-0 .scene-card-preview-video, .zoom-0 .scene-card-video, +.zoom-1 .scene-card-preview, .zoom-1 .scene-card-preview-video, .zoom-1 .scene-card-video, +.zoom-2 .scene-card-preview, .zoom-2 .scene-card-preview-video, .zoom-2 .scene-card-video, +.zoom-3 .scene-card-preview, .zoom-3 .scene-card-preview-video, .zoom-3 .scene-card-video { + object-fit: cover !important; + margin-top:-2%; + margin-left:-6px; + transform: scale(1.04); +} + +/* --- Shrink the Player Height just a little bit to avoid the scroll bar --- */ +#jwplayer-container { height: calc(99.5vh - 4rem);} + + +div.tagger-container .btn-link { + font-family: var(--HeaderFont); + font-size: 110%; + color: #ddf; + text-shadow: var(--std-txt-shadow); +} + +#scene-edit-details .edit-buttons-container { + background-color: rgba(0,0,0,0.0); + position: relative; + margin-bottom:15px; +} + +#scene-edit-details .form-group {margin-bottom:0.65rem;} + + + + + + +/* ============== Studio ================= */ + + +.studio-card { padding: 0 4px 14px;} + +.studio-details input.form-control-plaintext { background-color: rgba(16,22,26,.0); } +.studio-details .react-select__control--is-disabled { background: none; border:0} + +.studio-details .form-group, .studio-details td { padding: 8px; } +.studio-details table td:nth-child(1) {color:#FFF; text-shadow: 1px 1px 1px #000;} + +.studio-card-image {max-height: 175px; height:175px} +.studio-card-image {min-width: 260px; width: calc(238px + 19vw / 3.8); max-width: 360px; margin: 0 6px;} +.studio-card .card-section { margin-top: 22px;} +.studio-card .card-section div {color:#FFF} +.studio-card .card-popovers {width:63%;margin-left:37%} + +@media (min-width: 1200px) { +.pl-xl-5, .px-xl-5 { + padding-left: 1rem!important; + padding-right: 1rem!important; +} } + +.no-gutters .TruncatedText, .tag-card .TruncatedText, div.card.studio-card .TruncatedText, .tagger-container .TruncatedText { + font-family: var(--HeaderFont); + font-size: 125%; + line-height:125%; + font-weight:bold; + text-shadow: var(--std-txt-shadow); +} + +.no-gutters .TruncatedText {font-size: 115%;} + +/* --- The following 15 Selectors modify the info box on the left after clicking on a movie --- */ +.studio-details .text-input, #performer-details-tabpane-details .text-input:disabled, +.studio-details .text-input[readonly] {background-color: rgba(16,22,26,0.0);} + +.text-input, input.form-control-plaintext { background-color:none;} +.studio-details .input-control, .text-input {box-shadow: none;} + +.studio-details table { margin-top: 20px; background-color: rgba(15,20,30,0.20); border-radius: 10px; margin-bottom:20px;} +.studio-details .form-group, .studio-details .row {background-color: rgba(15,20,30,0.20); margin:0;} +.studio-details .no-gutters {background: none !important; } + +.studio-details table div.react-select__control {background: none; border: 0px;margin:0} +.studio-details table .css-1hwfws3 { padding:0px; } + +.studio-details .form-group, .movie-details td { padding: 8px; } +.studio-details .form-group td:nth-child(1), +.studio-details table tbody tr td:nth-child(1), td:first-child {padding-left: 12px; width: 130px;} + +.studio-details table tr:nth-child(odd) { background-color: rgba(16,22,26,0.1); } +.studio-details .form-group, .studio-details table td:nth-child(1) {color:#FFF; text-shadow: 1px 1px 1px #000;} + + +.studio-card.card .rating-1, .studio-card.card .rating-2, .studio-card.card .rating-3, +.studio-card.card .rating-4, .studio-card.card .rating-5 +{ + background:none; + height: 25px; + background-size: 135px 25px; + background-image:var(--stars); + -webkit-transform:rotate(0deg); + transform:rotate(0deg); + padding:0; + padding-bottom:1px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, .00); + left:10px; + text-align:left; + position:absolute; + top:auto; + bottom: 24% !important; + font-size:0.001rem; +} + +.studio-card.card .rating-5{width:135px;} +.studio-card.card .rating-4{width:109px;} +.studio-card.card .rating-3{width:81px;} +.studio-card.card .rating-2{width:55px;} +.studio-card.card .rating-1{width:28px;} + +div.studio-card.card .card-popovers { margin-top: -34px;} +.studio-card.card .card-section div:nth-child(2) {margin-bottom:6px;margin-top:-3px;} + +.studio-details dl.details-list{ grid-column-gap: 0} +.studio-details dt, .studio-details dd {padding: 6px 0 8px 8px} + + + + + + + + + +/* ============== TAGS =============== */ + +.tag-card.card hr, .tag-card.card>hr{ border-top: 0px solid rgba(0,0,0,.1); } + +.tag-card { margin: 4px 0.5% 10px; padding:0px;} + + +@media (min-width: 1200px){ +.row.pl-xl-5, .row.px-xl-5 { + padding-left: 0.2rem!important; + padding-right: 0.2rem!important; +} +} + +.tag-card.zoom-0 { + min-width: 230px; width: calc(200px + 18vw / 1.1); max-width: 350px; + min-height:168px; height:auto; +} +.tag-card.zoom-0 .tag-card-image { min-height: 100px; max-height: 210px; height: calc(95px + 15vw / 1.1);} + +.tag-card.zoom-1 { + min-width: 260px; width: calc(238px + 25vw / 2.3); max-width: 460px; + min-height:193px; height:auto; +} +.tag-card.zoom-1 .tag-card-image { min-height: 120px; max-height: 240px; height: calc(100px + 19vw / 2.3);} + +.tag-card.zoom-2 { + min-width: 290px; width: calc(280px + 38vw / 2.45); max-width: 650px; + min-height:170px; height:auto; max-height:485px; +} +.tag-card.zoom-2 .tag-card-image { min-height: 175px; max-height: 440px; height: calc(120px + 30vw / 2.45);} + + +#tags .card {padding:0 0 10px 0; } +.tag-card-header {height:190px;overflow:hidden;} + +.zoom-0 .tag-card-image, .zoom-1 .tag-card-image, .zoom-2 .tag-card-image { +zoom: 101%; +object-fit: cover; +overflow:hidden; +width: 101%; +margin-top: -2px; +margin-left: -1%; +} + +.tag-card .scene-popovers, .tag-card .card-popovers { + width:60%; + margin-left:40%; + justify-content: flex-end; + float:right; + margin-bottom: 15px; + margin-top:-34px; + padding-left:17px; +} + +.tag-sub-tags, .tag-parent-tags {margin-bottom:9px; color:#FFF} +.tag-sub-tags a, .tag-parent-tags a {color:#FFF; text-shadow: var(--std-txt-shadow)} + +.zoom-0 .tab-pane .card-image { height:210px } + + +/* --- Moves the Tag name into the Tag Picture --- */ +.tag-details .text-input[readonly] {background-color: rgba(0,0,0,.0);} +.tag-details .table td:first-child {display:none} +.tag-details .logo {margin-bottom: 12px;} + +.tag-details .form-control-plaintext, .tag-details h2 { + margin-top:-76px; + margin-left:0%; + font-weight: bold; + font-family: Helvetica, "Helvetica Neue", "Segoe UI" !important; + letter-spacing: 0.11rem; + font-size:44px; + text-shadow: 2px 2px 3px #111, 4px 4px 4px #282828, 6px 1px 4px #282828, -3px 3px 3px #444, -2px -2px 4px #282828; + text-align:center; +} +@media (max-width: 1300px) {.tag-details .form-control-plaintext {font-size:26px; margin-top:-50px;}} + +.tag-details .logo { min-width:300px} + + + + + +/* ============== MOVIES ============== */ + +/* --- Changes the width of the items so only the front cover is displayed. Also replaces the ratings banner with a star rating --- */ + +.movie-card .text-truncate, div.card.movie-card .TruncatedText { + font-size: 17px !important; + font-family: var(--HeaderFont); + text-shadow: var(--std-txt-shadow); + font-weight: bold; + max-width:210px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.movie-card.grid-card.card .card-section p {margin-bottom:-8px} +div.movie-card.grid-card.card .card-section {margin-bottom: -0px !important} +div.movie-card.grid-card.card .card-popovers { + padding-top:35px; + margin-bottom:-11px; + width:60%; + margin-left:40%; + justify-content:flex-end; + float:right; +} + +div.movie-card .card-section span {position:absolute;margin-top:-3px;margin-bottom:6px;color:#FFF} + + + +.movie-card-header {height:320px} + +.movie-card-header .rating-banner { + font-size: .002rem; + padding: 8px 41px 6px; + line-height: 1.1rem; + transform: rotate(0deg); + left: 3px; + top: 77% !important; + height: 25px; + background-size: 135px 25px; + background-position: left; +} + +.movie-card-header .rating-1 {width:28px} +.movie-card-header .rating-2 {width:55px} +.movie-card-header .rating-3 {width:83px} +.movie-card-header .rating-4 {width:110px} +.movie-card-header .rating-5 {width:138px} + +.movie-card-header .rating-5 { + background:none; + background-image:var(--stars); + height: 25px; + background-size: 135px 25px; +} + +.movie-card-image { + height:345px; + max-height: 345px; + width: 240px; +} + + + + +/* --- The following 15 Selectors modify the info box on the left after clicking on a movie --- */ +.movie-details .text-input, #performer-details-tabpane-details .text-input:disabled, +.movie-details .text-input[readonly] {background-color: rgba(16,22,26,0.0);} + +.text-input, input.form-control-plaintext { background-color:none;} +.movie-details .input-control, .text-input {box-shadow: none;} + +.movie-details table { margin-top: 20px; background-color: rgba(15,20,30,0.20); border-radius: 10px 10px 0px 0px; margin-bottom:0;} +.movie-details .form-group {background-color: rgba(15,20,30,0.20); margin:0;} + +.movie-details table div.react-select__control {background: none; border: 0px;margin:0} +.movie-details table .css-1hwfws3 { padding:0px; } + +.movie-details .form-group, .movie-details td { padding: 8px; } +.movie-details .form-group td:nth-child(1), +.movie-details table tbody tr td:nth-child(1), td:first-child {padding-left: 12px; width: 120px;} + +.movie-details table tr:nth-child(odd) { background-color: rgba(16,22,26,0.1); } +.movie-details .form-group, .movie-details table td:nth-child(1) {color:#FFF; text-shadow: 1px 1px 1px #000;} + + + +/* --- 0.60 dev adjustments --- */ +.studio-details .studio-details, .movie-details .movie-details {background-color: rgba(15,20,30,0.20); border-radius: 10px; margin-bottom:15px; } +.movie-details .movie-details dt.col-3 {padding:4px 0 4px 16px; width: 120px;} +.movie-details .movie-details dd.col-9 {padding:4px 16px 4px 3px;} +.studio-details dl.details-list dt:nth-of-type(odd), +.studio-details dl.details-list dd:nth-of-type(odd), +.movie-details dl.details-list dt:nth-of-type(odd), +.movie-details dl.details-list dd:nth-of-type(odd), +.movie-details dl.row:nth-child(odd) { background-color: rgba(16,22,26,0.1); margin-right:0px; padding-left:14px;margin-bottom:5px} +.movie-details dl.details-list dt, .movie-details dl.details-list dd {padding-left: 7px;} +.movie-details dl.details-list { grid-column-gap: 0} +.studio-details h2, .movie-details .movie-details h2 { font-family: var(--HeaderFont);font-weight:bold;text-shadow: var(--std-txt-shadow);padding:7px 0 5px 12px;} + +.movie-details .movie-images {margin:0 5px 20px 5px;} +.movie-details .movie-images img {border-radius: 14px; max-height:580px;} +.movie-details .movie-image-container{ + margin:0.3rem; + margin-right:0.8rem; + background-color: rgba(0, 0, 0, .48); + box-shadow: 6px 6px 11px rgba(0, 10, 10, .62); +} + +form#movie-edit { margin-bottom:15px} + + + + + + +/* ============== IMAGES ============== */ + +div.image-card .rating-banner { + font-size: .002rem; + padding: 8px 41px 6px; + line-height: 1.1rem; + transform: rotate(0deg); + padding: 0; + left: 3px; + top: 72% !important; + height: 25px; + background-size: 135px 25px; + background-position: left; +} + +div.image-card .rating-1 {width:28px} +div.image-card .rating-2 {width:55px} +div.image-card .rating-3 {width:83px} +div.image-card .rating-4 {width:110px} +div.image-card .rating-5 {width:138px} + +div.image-card .rating-5 { + background:none; + background-image:var(--stars); + height: 25px; + background-size: 135px 25px; +} + +div.image-card .scene-popovers, div.image-card .card-popovers { + margin-top: -2px; + justify-content: flex-end; +} +div.image-card hr, .scene-card.card>hr{ + border-top: 0px solid rgba(0,0,0,.1); +} + + + + + + + +/* ============== GALLERIES ============== */ + +div.gallery-card hr, .scene-card.card>hr{ + border-top: 0px solid rgba(0,0,0,.1); +} + +div.gallery-card .rating-banner { + font-size: .002rem; + padding: 8px 41px 6px; + line-height: 1.1rem; + transform: rotate(0deg); + padding: 0; + left: 3px; + top: 70% !important; + height: 25px; + background-size: 135px 25px; + background-position: left; +} + +div.gallery-card .rating-1 {width:28px} +div.gallery-card .rating-2 {width:55px} +div.gallery-card .rating-3 {width:83px} +div.gallery-card .rating-4 {width:110px} +div.gallery-card .rating-5 {width:138px} + +div.gallery-card .rating-5 { + background:none; + background-image:var(--stars); + height: 25px; + background-size: 135px 25px; +} + +div.gallery-card .scene-popovers, div.gallery-card .card-popovers { + margin-bottom: -8px; + margin-top: -24px; + justify-content: flex-end; +} + + + + + + + + +/* ============== MISC ============== */ + +.svg-inline--fa.fa-w-18 {width: 1.4em;} + +/* --- Makes the Zoom Slider on the Scenes, Images, Galleries and Tags pages longer and therefore easier to use --- */ +input[type=range].zoom-slider{ max-width:140px;width:140px; } + +/* --- Changes the zoom slider color --- */ +input[type=range]::-webkit-slider-runnable-track {background-color: #88afcc !important;} + + +.tag-details .logo { + background-color: rgba(0, 0, 0, .48); + box-shadow: 3px 3px 5px rgba(0, 0, 0, .42); + border-radius: 9px !important; +} + +.search-item { + background-color: none; + background-color: rgba(16,22,26,0.27); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + background-color: none; + background-color: rgba(16,22,26,0.67); +} + +/* --- Edit & Delete buttons when clicking on a studio, tag or movie --- */ +.details-edit {margin-left:3%} + +/* --- Adds a text shadow to the statistics on the startpage --- */ +.stats .title { + color:#fff; + text-shadow: 2px 2px 4px #282828; +} + + +.popover { + padding:2px; + background-color: rgba(5,30,35,0.85); + box-shadow: 3px 3px 6px rgba(20, 20, 20, .8); +} +.hover-popover-content { + background-image: linear-gradient(160deg, rgba(230,255,240,0.80), rgba(120,130,155, 0.45), rgba(180,190,225, 0.45), rgba(120,130,165, 0.55), rgba(255,235,235,0.70)); + background-color: rgba(205,210,225,0.31) !important; +} + +.tag-item { + font: normal 13px "Lucida Grande", sans-serif, Arial, Verdana; + background-image: linear-gradient(210deg, rgba(30,95,140,0.36), rgba(10,60,95, 0.45), rgba(20,65,105, 0.83), rgba(5,90,140,0.35)); + background-color: rgba(60,120,230,0.8); + color: #fff; + letter-spacing: 0.07rem; + line-height: 18px; + margin: 3px 3px; + padding: 6px 8px; +} + +/* --- Adjust the lengths of the Performer, Movies and Tags fields while editing a scene while the scene plays --- */ +#scene-edit-details .col-sm-9 { + flex: 0 0 100%; + max-width: 100%; +} + +/* --- Cuts the name of Performers, Movies and Tags short if they go longer than the length of the field --- */ +div.react-select__control .react-select__multi-value { + max-width: 285px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + + + +/* --- several Performer and Scene Scaping changes --- */ +.modal-content, .modal-lg, .modal-xl { + max-width: 1400px; + width:100%; +} + +.modal-header, .modal-body, .modal-footer { background: rgba(10,120,170,0.94);} +.performer-create-modal {max-width:1300px;} + +.modal-body .col-form-label, .modal-body .col-6, .modal-footer, .modal-header .col-form-label {text-shadow: var(--std-txt-shadow);} + +.modal-body .col-6 strong {font-weight: normal; font-size:14px} +.modal-body .no-gutters {margin-bottom: 8px} + +.modal-body .dialog-container .col-lg-3 { + flex: 0 0 12%; + max-width: 12%; + text-shadow: var(--std-txt-shadow); +} + +.modal-body .dialog-container .offset-lg-3{margin-left:12%;} +.modal-body .dialog-container .col-lg-9{flex:0 0 88%; max-width:88%;} + + +.input-control, .input-control:disabled, .input-control:focus, .modal-body div.react-select__control, .modal-body .form-control { + background: rgba(15,15,20,0.36); +} + + +.scraper-table tr:nth-child(2n) { background: rgba(15,15,20,0.18);} + +.nav-pills .nav-link.active, .nav-pills .show>.nav-link { background: rgba(15,15,20,0.50);} + + +.btn-secondary:not(:disabled):not(.disabled).active, +.btn-secondary:not(:disabled):not(.disabled):active, +.show>.btn-secondary.dropdown-toggle { background: rgba(15,15,20,0.50);} + + +/* --- Background when searching for a scene in Tagger view --- */ +.search-result { background: rgba(0,0,0,0.21);} +.selected-result { background: rgba(25,95,25,0.24); color:#fff} +/*.search-result:hover { background: rgba(12,62,75,0.25);}*/ +.search-result:hover { background: rgba(25,122,25,0.33);} + + +.markdown table tr:nth-child(2n) {background: rgba(25,20,25,0.20);} +.markdown code, .markdown blockquote, .markdown pre {background: rgba(25,20,25,0.30);} + + +/* --- Changes the size of the Custom CSS field in the settings --- */ +#configuration-tabs-tabpane-interface textarea.text-input { min-width:60ex; max-width:55vw !important; min-height:50ex;} + + +div.dropdown-menu,div.react-select__menu{background-color:rgba(35,37,44,0.55);color:#f5f8fa} + +div.dropdown-menu .dropdown-item, div.dropdown-menu .react-select__option, div.react-select__menu .dropdown-item, div.react-select__menu .react-select__option +{color:#f5f8fa;background-color:rgba(35,37,44,0.55);} + +div.dropdown-menu .dropdown-item:focus,div.dropdown-menu .dropdown-item:hover,div.dropdown-menu .react-select__option--is-focused,div.react-select__menu .dropdown-item:focus,div.react-select__menu .dropdown-item:hover,div.react-select__menu .react-select__option--is-focused{background-color:rgba(24,130,195,0.85)} + + +.toast-container { + left: 74%; + top: 1rem; +} + +/* --- Settings / About adjustments --- */ +#configuration-tabs-tabpane-about .table {width:100%} +#configuration-tabs-tabpane-about .table td {padding-top:18px} + + +#queue-viewer .current { + background-color: rgba(15,20,30,0.30); +} + +/* Folder when doing selective scan or configuration */ +.input-group .form-control {color: #d9f0f7; } + + +div.modal-body b, .form-label h6 {text-shadow: var(--std-txt-shadow);} + + +.modal-body .btn-primary:not(:disabled):not(.disabled).active, .modal-body .btn-primary:not(:disabled):not(.disabled):active { +color: #fff; + background-color: #008558; + border-color: #0d5683; +} + +.modal-body .btn-primary { + color: #fff; + background-color: #666; + border-color: #666; +} + +.input-group-prepend div.dropdown-menu +{ + background: rgba(50,90,105,0.94); + padding:15px; + box-shadow: 2px 2px 6px rgba(0, 0, 0, .70); +} + +.saved-filter-list .dropdown-item-container .dropdown-item {margin-top:3px;} +.set-as-default-button {margin-top: 8px;} + + + +/* --- Specific Light Modifications --- */ + +a.minimal, button.minimal { + color: #fff; + text-shadow: 1px 1px 2px #000; +} + +.top-nav a.minimal, .top-nav button.minimal { + color: #333; + text-shadow: 1px 1px 2px #ddd; +} + +.top-nav { box-shadow: 2px 2px 6px rgba(120, 150, 160, .55)} + + +.pagination .btn:last-child {border-right: 1px solid #202b33;} +.pagination .btn:first-child {border-left: 1px solid #202b33;} + +.nav-tabs .nav-link { color: #000038;} + + +.scene-card .card-section {color:#fff;text-shadow: 0px 0px 1px #111;} +.changelog-version, .tab-content {color: #fff} +.tab-content .bg-dark {color:#fff} + +#performer-details-tabpane-details {color: #000} +.scraper-table a {color:#dde} +.image-tabs, .gallery-tabs, .scene-tabs { + background-image: linear-gradient(130deg, rgba(60, 70, 85,0.21), rgba(150, 155, 160,0.30), rgba(35, 40, 45,0.22), rgba(160, 160, 165,0.21), rgba(50, 60, 65,0.30)); + background-color: rgba(106, 120, 125, .29); +} + +.table-striped.table-bordered, .table-striped.table-bordered a {color:#000} + +.nav-tabs .nav-link.active { color: #2866d0;} + +.PerformerScrapeModal-list .btn-link { color: #a9d0ff;} + +.tab-content .scene-file-info a {color: #cde} + +.grid-card .card-check { top:.9rem;width: 1.5rem;} +.LoadingIndicator .spinner-border, .LoadingIndicator-message { color:#37e} + + +.btn-group>.btn-group:not(:last-child)>.btn, .btn-group>.btn:not(:last-child):not(.dropdown-toggle), +.pagination .btn-group>.btn:first-child:not(.dropdown-toggle) {border-left: 1px solid #394b59;} +.btn-group>.btn-group:not(:first-child), .btn-group>.btn:not(:first-child) {border-right: 1px solid #394b59;} + + +div.gallery-card.grid-card.card p div.TruncatedText, +div.movie-card.grid-card.card hr, div.movie-card.grid-card.card p {display:none} + + +/* --- Spacing out the paginationIndex --- */ +.paginationIndex {color:#f3f3f3;margin-bottom:8px} +.paginationIndex .scenes-stats, .images-stats {margin-top:-3px; color:#aaaab9} +.paginationIndex .scenes-stats:before, .images-stats:before +{ + font-size: 16px; + margin-left:18px; + margin-right:12px; + color:#ccc; + content: "-"; +} + +/* --- Overhaul of the Popoup Edit Boxes --- */ +@media (min-width: 576px) { + #setting-dialog .modal-content .modal-body textarea {min-height:350px; height:75vh !important} + .modal-dialog {max-width: 880px} + .modal-dialog .modal-content .form-group .multi-set {width:82%;margin-top:12px; flex: 0 0 82%; max-width:82%;} + .modal-dialog .modal-content .form-group .col-9 {flex: 0 0 82%;max-width: 82%;} + .modal-dialog .modal-content .col-3 { flex: 0 0 18%; max-width: 18%;} + .modal-dialog .modal-content .form-group > .form-label {margin-top:0px;flex: 0 0 18%; max-width: 18%;text-shadow: var(--std-txt-shadow);} + .modal-dialog .modal-content .form-group {display: flex; flex-wrap: wrap;} + .modal-dialog .modal-content .btn-group>.btn:not(:first-child), .btn-group>.btn-group:not(:first-child) {margin-left: 2px} + .modal-dialog .modal-content .form-label[for~="movies"], + .modal-dialog .modal-content .form-label[for~="tags"], + .modal-dialog .modal-content .form-label[for~="performers"] {margin-top:48px;} + .modal-dialog .modal-content .button-group-above {margin-left:9px} + .modal-dialog .scraper-sources.form-group h5 {margin-top:20px} + .modal-dialog .modal-content .field-options-table {width:98%} + + .modal-dialog.modal-lg .modal-content .form-group {display: inline;} +} + + + + + + + +/* ============== SETTINGS ============== */ + + + +#settings-container { + padding-left:230px; + background-image: none !important; + background-color: rgba(16, 20, 25, .40) !important; + box-shadow: 2px 2px 7px rgb(0 0 0 / 75%); + border-radius: 10px; + padding-top:25px; + min-height:450px; +} + +#settings-container .card { + margin-bottom:25px; + background-image: none !important; + background-color: rgba(16, 20, 25, .00); + box-shadow: 0px 0px 0px rgb(0 0 0 / 75%); + border-radius: 0px; +} + +#settings-container .bg-dark {background-color: rgba(16, 20, 25, .12) !important;} + +.form-group>.form-group {margin-top:0.5em; margin-bottom: 0.5rem} + + +#configuration-tabs-tabpane-tasks>.form-group {margin-bottom:2rem; margin-top:1em} + +#configuration-tabs-tabpane-tasks h6 { margin-top:3.5em; font-weight:bold; margin-bottom:1em; } +#configuration-tabs-tabpane-tasks h5 { margin-top:2.0em; font-weight:bold; letter-spacing: 0.09rem; } + +.form-group h4 {margin-top:2em} + + +#parser-container.mx-auto {max-width:1400px;margin-right:auto !important} +.scene-parser-row .parser-field-title {width: 62ch} + + + +.mx-auto {margin-right: 1% !important} +.mx-auto.card .row .col-md-2 .flex-column { position:fixed;min-height:400px} +.mx-auto.card>.row {min-height:360px} + +.loading-indicator {opacity:80%; zoom:2} + + + + +/* --- Settings - Tasks ------------------------------------------------------------------------------------- */ + + +#configuration-tabs-tabpane-tasks>.form-group .card { + padding: 20px; + margin: 4px 0.40% 14px; + background-image: none; + background-color: rgba(16, 20, 25, .00); + box-shadow: none; + border-radius: 10px; +} + +#tasks-panel h1 {margin-top: 3em} +.setting-section h1, #tasks-panel h1 {font-size: 1.55rem; max-width:180px} + + +@media (min-width: 576px) and (min-height: 600px) { +#tasks-panel .tasks-panel-queue { + background: none !important; + margin-top: -2.6rem; + padding-bottom: .25rem; + padding-top: 0rem; + position: relative; + top: 0rem; + z-index: 2; +} +} + +#tasks-panel hr {border-top: 0px solid rgba(140,142,160,.38);} +#tasks-panel h1 {margin-top:1.8em;} +#tasks-panel h1 {margin-top:0.8em;} + +#configuration-tabs-tabpane-tasks {margin-top:40px} + +#configuration-tabs-tabpane-tasks .form-group:last-child .setting-section .setting div:last-child { + margin-right: 0% !important; + margin-left: 0px; + margin-top: 2px; +} + +#configuration-tabs-tabpane-tasks .setting-section .sub-heading {margin-bottom:1em} +#configuration-tabs-tabpane-tasks .setting-section .collapsible-section {margin-bottom:3em} +#configuration-tabs-tabpane-tasks #video-preview-settings button { width:250px;margin-top:22px;margin-left:-57px} +#configuration-tabs-tabpane-tasks .tasks-panel-tasks .setting-section:nth-child(3) {margin-top:5em} + +.tasks-panel-tasks .setting a { color: #ccccd3;} + + + + + + + + + + + + + + + + + + +@media (min-width: 1000px) { + #settings-container .card {margin-top:-43px; margin-left:255px} +} + + + +#settings-container .col-form-label { + padding-top: calc(.55rem); + padding-bottom: calc(.55rem); +} + +.setting-section .setting-group>.setting:not(:first-child), .setting-section .setting-group .collapsible-section .setting { + margin-left: 4rem; +} + + + +.setting-section .setting h3 { + font-size: 1.4rem; + margin:0.6em 0 0.4em; + +} + +.setting-section:not(:first-child) {margin-top: 1em} +.setting-section .setting-group>.setting:not(:first-child).sub-setting, .setting-section .setting-group .collapsible-section .setting.sub-setting { padding-left: 3rem;} + + + +@media (min-width: 1200px) { + .offset-xl-2 { + max-width:1250px; + margin-left:15%; + margin-right:auto; + } + + #settings-container .tab-content, .mx-auto { max-width: none;} +} + + +.setting-section .setting:not(:last-child) { + border-bottom: 0px solid #000; +} + + + + +.job-table.card { + margin-top:-32px !important; + background-color: rgba(16, 20, 25, .20) !important; + border-radius: 10px !important; +} + + + + + + +.custom-switch {padding-left:2.25rem} +.custom-control { + min-height: 1.5rem; + padding-left: 0.5rem; + margin-right:1em; +} + +.custom-control-input:checked~.custom-control-label:before { + color: rgb(0 0 0 / 0%); + border-color: rgb(0 0 0 / 0%); + background-color: rgb(0 0 0 / 0%); +} + +.custom-switch .custom-control-label:before { + pointer-events: auto; + border-radius: 0; +} + +.custom-switch .custom-control-input:checked~.custom-control-label:after { + background-color: blue; + transform: auto; +} + +.custom-switch .custom-control-label:after { + top: auto; + left: auto; + width: auto; + height: auto; + background-color: blue; + border-radius: 0; + transform: none; + transition: none; +} + +.custom-control-label:before {display:none} + +.custom-control-input { + position: absolute; + top:2px; + left: 0; + z-index: -1; + width: 1.2rem; + height: 1.35rem; + opacity: 1; + background-color:#10659a; + color:#10659a; +} + + + + +.setting-section .setting-group>.setting:not(:first-child), .setting-section .setting-group .collapsible-section .setting { + padding-bottom: 3px; + padding-top: 4px; + margin-right: 0rem; +} + +.setting-section {margin-bottom:0.8em} + + + + +.setting-section .sub-heading { + font-size:.9rem; + margin-top:0.5rem; + margin-bottom:3rem; +} + +@media (min-width: 768px) { +.offset-md-3 {margin-left: 1%;} +#settings-menu-container {margin-left:1%; z-index:9; width:200px; padding-top:25px;} + + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div:nth-child(2) { width:64%;min-width:300px;padding-top:6px} + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div .sub-heading {margin-top:-28px; margin-left:255px;width:700px} + #language .input-control { width:250px} + + #configuration-tabs-tabpane-interface .setting-section .setting-group>.setting:not(:first-child) {margin-left:275px} + #configuration-tabs-tabpane-interface .setting-section .setting-group>.setting:nth-child(2) {margin-top: -40px} +} + +@media (min-width: 1200px) { + .offset-md-3 {margin-left: 14%;margin-right:2%} + .setting-section h1, #tasks-panel h1 { max-width:220px;} + #settings-menu-container { + padding-top:25px; + margin-left:14% !important; + z-index:9; + width:205px; + } +} + +@media (max-width: 768px) { + .offset-md-3 {margin-left: 1%;} + #settings-menu-container {margin-left:1%; z-index:9;width:180px; padding-top:25px;} + #settings-container { padding-left: 180px;} + .setting-section h1, #tasks-panel h1 { max-width:300px;} +} + +@media (max-width: 576px) { + .offset-sm-3 {margin-left: 1%;} + #settings-menu-container {margin-left:1%;z-index:9;width:160px; padding-top:25px;} + #settings-container {padding-left: 10px;} +} + +@media (max-width: 1004px) { + .setting-section h1, #tasks-panel h1 { max-width:400px;} + .job-table.card {margin-top:2px !important;} +} + + + + +.markdown table tr:nth-child(2n), +.modal-body .nav-link:hover, +#settings-menu-container .nav-link:hover {background-color: rgba(10, 20, 20, .15)} + + + +@media (min-width: 1000px) { + #settings-container #configuration-tabs-tabpane-interface .setting-section > .setting { padding: 15px 0px;} + #settings-container #configuration-tabs-tabpane-interface .setting-section .setting-group .setting>div:first-item{margin-left: 4% !important;} + + #settings-container #stash-table {margin-top:25px} + #configuration-tabs-tabpane-interface .setting-section:first-child .card { margin-top: -5px; margin-left: -1%} + + #language .input-control { width:250px} + #configuration-tabs-tabpane-interface .setting-section:first-child h3 { font-size: 1.55rem;} + + #configuration-tabs-tabpane-interface .setting-section:first-child .card > .setting div:nth-child(1) { width:255px} + + + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div:nth-child(2) { width:68%;padding-top:6px} + #configuration-tabs-tabpane-interface .setting-section:first-child .card .setting div .sub-heading {margin-top:-28px; margin-left:255px;width:700px} + + #configuration-tabs-tabpane-interface #language {margin-bottom:15px} + #configuration-tabs-tabpane-library #stashes .sub-heading {margin-top:-26px; margin-left:235px;width:700px} + +} + + + +#configuration-tabs-tabpane-metadata-providers .setting, +#configuration-tabs-tabpane-security .setting, +#configuration-tabs-tabpane-tasks .setting, +#configuration-tabs-tabpane-system .setting-section .setting, +#settings-dlna .setting-section .setting, +#configuration-tabs-tabpane-interface .setting-section .setting {padding-top:0; padding-bottom:0} + + +#configuration-tabs-tabpane-interface .setting-section:nth-child(1) h1 {display:none} + +#configuration-tabs-tabpane-interface .setting-section .setting-group>.setting:not(:first-child) h3 { + font-size: 1rem; + margin-left:2em; +} + +#configuration-tabs-tabpane-interface .setting-section .setting-group .setting>div:last-child { + margin-right: 95% !important; + margin-left:0px; + margin-top:-32px; +} + +.setting-section .setting>div:first-child {max-width:415px} + +#configuration-tabs-tabpane-interface .setting-section .setting>div:last-child { + min-width: 20px; + text-align: left; + width:38%; + +} + +#configuration-tabs-tabpane-interface h3 {font-size:1.25em} + +#wall-preview .input-control {width:160px} + +.setting-section .setting-group>.setting:not(:first-child), .setting-section .setting-group .collapsible-section .setting { + padding-top: 0px; + padding-bottom: 0px; + margin-right: 0rem; + line-height:100%; + margin-top:-3px; + margin-bottom:-4px; +} + +#configuration-tabs-tabpane-interface .setting-section:nth-child(7) .setting {margin-left:15px !important} +#configuration-tabs-tabpane-interface .setting-section:nth-child(7) .setting:nth-child(1) {margin-left: 0px !important;} + + +#settings-dlna h5 {margin-bottom:70px} +#settings-dlna .form-group h5{margin-left:255px;margin-top:-30px} + +#configuration-tabs-tabpane-metadata-providers #stash-boxes .sub-heading {margin-top:-28px; margin-left:235px;width:700px;font-size:14px} + +.scraper-table tr:nth-child(2n) {background-color: rgba(16, 20, 25, .12)} + + + +/* Library */ + +.stash-row .col-md-2 {padding-left:4%} +#configuration-tabs-tabpane-library .setting-section .setting {padding:0} + + + +#configuration-tabs-tabpane-security .setting-section, +#configuration-tabs-tabpane-tasks .setting-section, +#configuration-tabs-tabpane-tasks .setting-group{max-width:915px} + +#configuration-tabs-tabpane-logs .setting-section, +#configuration-tabs-tabpane-metadata-providers .setting-section, +#configuration-tabs-tabpane-services .setting-section, +#configuration-tabs-tabpane-system .setting-section, +#configuration-tabs-tabpane-library .setting-section:not(:first-child), +#configuration-tabs-tabpane-interface .setting-section {max-width:810px} + +#configuration-tabs-tabpane-security .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-metadata-providers .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-services .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-system .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-library .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-interface .setting-section .setting>div:last-child, +#configuration-tabs-tabpane-tasks .setting-section .setting>div:last-child { + min-width: 20px; + text-align: right; + width:auto; + +} + +#configuration-tabs-tabpane-tasks .setting-section .collapsible-section .setting div:last-child { + margin-right: 95% !important; + margin-left: -12px; + margin-top: -15px; +} + + +#configuration-tabs-tabpane-system .setting-section .sub-heading {margin-bottom: 1.2rem} + + diff --git a/themes/Theme-PulsarLight/Theme-PulsarLight.yml b/themes/Theme-PulsarLight/Theme-PulsarLight.yml new file mode 100644 index 00000000..12bd3dd9 --- /dev/null +++ b/themes/Theme-PulsarLight/Theme-PulsarLight.yml @@ -0,0 +1,6 @@ +name: Theme - PulsarLight +description: Plex Theme for Stash by Fonzie 2021 +version: 0.3.1 +ui: + css: + - Theme-PulsarLight.css From 176a50e47c05ee41a3a101074a4a1ffe0fb1b948 Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 13:30:20 -0500 Subject: [PATCH 24/91] update readme for themes --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8fc2cef6..f5511aae 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ We're beginning to review plugins and the rest and patch them to work, but it's We'll update the table below as we do this... We will also be rearranging things a bit, and updating documentation (including this page) -## Plugins and Themes will no longer be listed here. +## Plugins will no longer be listed individually here... Category|Triggers|Plugin Name|Description|Minimum Stash version|Updated for v24| --------|-----------|-----------|-----------|---------------------|----- @@ -42,9 +42,8 @@ Reporting||[TagGraph](plugins/tagGraph)|Creates a visual of the Tag relations.|v ## Themes -Theme Name|Description |Updated for v24| -----------|--------------------------------------------|---- -[Plex](themes/plex) |Theme inspired by the popular Plex Interface|:x: +# A Variety of Themes are now available to be one click installed via the Plugin Setting page in your Stash +We welcome new themes, as well as patches to existing themes. ## Utility Scripts From 9542da3eb3b5dc97a4a04bea5a45455e0fcceee7 Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 13:38:42 -0500 Subject: [PATCH 25/91] add theme directory to build script, so it adds the themes --- build_site.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_site.sh b/build_site.sh index 463ee690..93857f95 100755 --- a/build_site.sh +++ b/build_site.sh @@ -69,4 +69,6 @@ buildPlugin() find ./plugins -mindepth 1 -name *.yml | while read file; do buildPlugin "$file" +find ./themes -mindepth 1 -name *.yml | while read file; do + buildPlugin "$file" done From 9f96f59de45338d543ab2eb70b088fc07fb545e6 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Mon, 1 Jan 2024 13:45:56 -0500 Subject: [PATCH 26/91] Fix urls in README.md --- themes/Theme-Plex/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/Theme-Plex/README.md b/themes/Theme-Plex/README.md index 386bbbbe..84f26ce6 100644 --- a/themes/Theme-Plex/README.md +++ b/themes/Theme-Plex/README.md @@ -24,6 +24,6 @@ _These steps are optional, by default this theme uses the Github hosted image li 2. Place `background.png` and `noise.png` in `~/.stash` on macOS / Linux or `C:\Users\YourUsername\.stash` on Windows. Then edit the `background-image: url("")` attributes like below: -The [body](https://github.com/stashapp/CommunityScripts/blob/main/themes/Theme-Plex/plex.css#L7) one `background-image: url("./background.png");` +The [body](https://github.com/stashapp/CommunityScripts/blob/main/themes/Theme-Plex/Theme-Plex.css#L7) one `background-image: url("./background.png");` -The [root](https://github.com/stashapp/CommunityScripts/blob/main/themes/Theme-Plex/plex.css#L18) one `background: rgba(0, 0, 0, 0) url("./noise.png") repeat scroll 0% 0%;` +The [root](https://github.com/stashapp/CommunityScripts/blob/main/themes/Theme-Plex/Theme-Plex.css#L18) one `background: rgba(0, 0, 0, 0) url("./noise.png") repeat scroll 0% 0%;` From 2ca3623b9fddde382ca5dfeed2c46af253a66914 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Mon, 1 Jan 2024 13:48:39 -0500 Subject: [PATCH 27/91] Update build_site.sh stray extra line... From 07de1ac8ba8c37a3bdb2d81196417ebbc0ed54d9 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Mon, 1 Jan 2024 13:51:16 -0500 Subject: [PATCH 28/91] helps to close your loops... --- build_site.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/build_site.sh b/build_site.sh index 93857f95..ee9d11d8 100755 --- a/build_site.sh +++ b/build_site.sh @@ -69,6 +69,7 @@ buildPlugin() find ./plugins -mindepth 1 -name *.yml | while read file; do buildPlugin "$file" +done find ./themes -mindepth 1 -name *.yml | while read file; do buildPlugin "$file" done From 646143fe87b059a8df88732417e6248c73351868 Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 14:16:02 -0500 Subject: [PATCH 29/91] add PH theme --- themes/Theme-PornHub/Theme-PornHub.css | 940 +++++++++++++++++++++++++ themes/Theme-PornHub/Theme-PornHub.yml | 6 + 2 files changed, 946 insertions(+) create mode 100644 themes/Theme-PornHub/Theme-PornHub.css create mode 100644 themes/Theme-PornHub/Theme-PornHub.yml diff --git a/themes/Theme-PornHub/Theme-PornHub.css b/themes/Theme-PornHub/Theme-PornHub.css new file mode 100644 index 00000000..20896550 --- /dev/null +++ b/themes/Theme-PornHub/Theme-PornHub.css @@ -0,0 +1,940 @@ +/* Author: ronilaukkarinen */ +/* stylelint-disable selector-max-specificity, declaration-no-important, selector-type-no-unknown, selector-max-class, a11y/no-outline-none, no-descending-specificity, selector-max-pseudo-class, property-disallowed-list, font-weight-notation, max-line-length, a11y/no-display-none, a11y/font-size-is-readable */ +/* Pornhub inspired stash theme */ +/* Fonts */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); + +/* Variables */ +:root { + --color-black: #000; + --color-text: #c6c6c6; + --color-text-dim: #969696; + --color-border: var(--color-black); + --color-hubs: #f90; + --color-cod-gray: #151515; + --color-silver: #cacaca; + --color-dark: #1b1b1b; + --color-dim: #2f2f2f; + --color-icon-toggled: #5faa01; + --color-star: #f5c518; + --color-white: #fff; + --color-favorite: #c71d1d; +} + +body { + background-attachment: fixed; + background-color: var(--color-black); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + color: var(--color-text); + font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif; + height: 100%; + padding: 6rem 0 0; + /* background-image: url("https://user-images.githubusercontent.com/63812189/79506691-4af78900-7feb-11ea-883e-87b8e05ceb1c.png"); */ + width: 100%; +} + +body.login { + padding-top: 0 !important; +} + +@media (max-width: 1200px) { + body { + padding-top: 0 !important; + } + + .main.container-fluid { + padding-top: 2.2rem !important; + } +} + +.top-nav .navbar-toggler { + width: auto !important; +} + +.top-nav a[href="/scenes/new"] button.btn { + color: var(--color-hubs); + font-size: 14px !important; +} + +body .badge, +body a, +body .tag-item { + transition: all 100ms; +} + +body div, +body p, +body a { + font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif; +} + +.show-carat.dropdown .btn, +.fa-icon, +.btn.minimal { + color: var(--color-silver) !important; +} + +.rating-stars .set { + color: var(--color-star) !important; +} + +.organized-button.organized svg { + color: var(--color-icon-toggled) !important; +} + +.favorite svg, +.favorite { + color: var(--color-favorite) !important; + filter: none !important; +} + +.thumbnail-section .favorite svg { + height: 20px; + width: 20px; +} + +.show-carat.dropdown .btn, +.btn.minimal { + background-color: transparent !important; + border: 0 !important; +} + +#root { + height: 100%; + /* background: rgba(0, 0, 0, 0) url("https://user-images.githubusercontent.com/63812189/79506696-4c28b600-7feb-11ea-8176-12a46454d87a.png") repeat scroll 0% 0%; */ + position: absolute; + width: 100%; + z-index: 2; +} + +* { + scrollbar-color: hsl(0deg 0% 100% / .2) transparent; +} + +.bg-dark { + background-color: var(--color-black) !important; +} + +.card { + background-color: var(--color-black); + background-color: rgb(0 0 0 / .3); + border-radius: 3px; + box-shadow: 0 0 0 1px rgb(16 22 26 / .4), 0 0 0 rgb(16 22 26 / 0), 0 0 0 rgb(16 22 26 / 0); + padding: 20px; +} + +.bg-secondary { + background-color: var(--color-black) !important; +} + +.pagination .btn { + align-items: center; + appearance: none; + background-color: var(--color-cod-gray) !important; + border: none; + border-radius: 4px; + color: var(--color-text) !important; + cursor: pointer; + display: inline-flex; + font-size: 20px !important; + font-weight: 700; + height: 60px !important; + justify-content: center; + margin: 0 4px !important; + min-width: 60px !important; + outline: 0 none; + padding: 0 20px; + position: relative; + text-align: center; + text-decoration: none; + vertical-align: top; +} + +.pagination .btn:hover, +.pagination .btn:focus { + background-color: var(--color-dim) !important; +} + +.pagination .btn:first-of-type, +.pagination .btn:last-of-type { + background: var(--color-black) !important; +} + +.pagination .btn.active { + background: var(--color-hubs) !important; +} + +.text-white, +body p { + color: var(--color-text) !important; +} + +.border-secondary { + border-color: var(--color-border) !important; +} + +.btn-secondary.filter-item.col-1.d-none.d-sm-inline.form-control { + background-color: rgb(0 0 0 / .15); +} + +.btn.active, +.btn-secondary { + color: var(--color-black) !important; + font-weight: 700; +} + +.btn-primary.disabled, +.btn-primary:disabled, +.btn-primary:hover, +.btn-primary:focus, +.btn-primary.focus, +minimal.w-100.active.btn.btn-primary, +.btn-primary { + background-color: var(--color-hubs); + border-color: var(--color-hubs); + box-shadow: none !important; + color: var(--color-black); + font-weight: 700; + outline: 0 !important; +} + +.nav-tabs .nav-link.active:hover, +.nav-tabs .nav-link.active:focus { + border-bottom-color: var(--color-text); + outline: 0; +} + +.nav-tabs .nav-link { + outline: 0; +} + +.btn-primary:not(:disabled):not(.disabled).active, +.btn-primary:not(:disabled):not(.disabled):active, +.show > .btn-primary.dropdown-toggle { + border-color: var(--color-text); + color: var(--color-text); +} + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + background-color: var(--color-black); +} + +input[type="range"]::-moz-range-track { + background: hsl(0deg 0% 100% / .25); +} + +input[type="range"]::-moz-range-thumb { + background: #bcbcbc; +} + +div.react-select__control { + background-color: hsl(0deg 0% 39.2% / .4); + border-color: #394b59; + color: #182026; + cursor: pointer; +} + +.navbar-nav a, +.navbar-nav button { + background: none !important; + border: 0 !important; + box-shadow: none !important; +} + +.TruncatedText { + line-height: 1.3; +} + +.navbar-nav { + gap: 1rem; +} + +.scene-wall-item-text-container { + background: radial-gradient(farthest-corner at 50% 50%, rgb(50 50 50 / .5) 50%, #323232 100%); + color: #eee; +} + +body .scene-header { + color: var(--color-white); + font-size: 17px; + font-weight: 700; + word-break: break-word; + word-wrap: break-word; +} + +.filter-container, +.operation-container { + box-shadow: none; + color: var(--color-text-dim) !important; + margin-top: -10px; + padding: 10px; +} + +.container-fluid, +.container-lg, +.container-md, +.container-sm, +.container-xl { + margin-left: 0; + margin-right: 0; + width: 100%; +} + +.btn-link { + color: #eee; + font-weight: 500; + text-decoration: none; +} + +button.minimal.brand-link.d-none.d-md-inline-block.btn.btn-primary { + font-weight: bold; + text-transform: uppercase; +} + +a:hover, +a:focus { + color: hsl(0deg 0% 100% / .7); +} + +option { + background-color: var(--color-black); + color: var(--color-white); +} + +select:focus > option:checked, +select option:hover, +option:checked, +option:hover, +option:focus { + background-color: var(--color-hubs) !important; + color: var(--color-black) !important; +} + +.folder-list .btn-link { + color: #2c2e30; +} + +#performer-scraper-popover { + z-index: 10; +} + +body { + background: var(--color-black) !important; +} + +.card { + background: var(--color-black) !important; + border-radius: 0 !important; + box-shadow: none !important; +} +/* .btn-primary { + color: #fff; + background-color: #131313 !important;; + border-color: #131313 !important;; +} */ + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + background-color: #131313 !important; + border-color: #131313 !important; +} + +#tasks-panel .tasks-panel-queue { + background-color: var(--color-black) !important; +} + +.top-nav .bg-dark, +.top-nav.navbar { + background: var(--color-dark) !important; +} + +body .top-nav { + border-radius: 0 !important; + padding: 10px 15px !important; +} + +.top-nav .btn { + border: 0 !important; + border-radius: 0 !important; + display: inline-flex !important; + flex-direction: row !important; + flex-wrap: nowrap !important; + font-size: 16px !important; + font-weight: 400 !important; + gap: 8px; + padding: 6px 8px !important; +} + +.top-nav .btn:hover svg, +.top-nav .btn:focus svg, +.top-nav .btn:hover, +.top-nav .btn:focus { + color: var(--color-white) !important; + opacity: 1 !important; +} + +.top-nav .btn .fa-icon { + margin: 0 !important; +} + +body .top-nav .navbar-brand { + margin-left: 0 !important; + margin-right: 1.5rem; + padding: 0 !important; +} + +body .top-nav .navbar-brand button { + color: var(--color-white) !important; + display: inline-block !important; + font-weight: 700 !important; + padding: 0 !important; +} + +body .top-nav .navbar-brand button::after { + background: var(--color-hubs); + border-radius: 3px; + color: var(--color-black); + content: 'hub'; + display: inline-block; + font-weight: 700 !important; + margin: 0 0 0 3px; + padding: 0 2px; +} + +.top-nav .btn.active { + border: 0 !important; + color: var(--color-white) !important; +} + +.navbar-dark .navbar-toggler { + border-color: transparent !important; +} + +button[title="Donate"] { + display: none !important; +} + +* { + outline: none !important; +} + +.grid-card a .card-section-title, +.section-title { + color: var(--color-text) !important; + font-size: 15px !important; + line-height: 17px !important; +} + +.card-section > a + span, +.card-section > a + span + p { + color: var(--color-text-dim) !important; +} + +.bg-secondary, +.btn-secondary, +.border-secondary { + background-color: var(--color-hubs) !important; + border-color: var(--color-black) !important; +} + +/* [Scenes tab] Allow for longer string when displaying "Studio as Text" on scene thumbnails */ +.scene-studio-overlay { + font-weight: 600 !important; + opacity: 1 !important; + text-overflow: ellipsis !important; + width: 60% !important; +} + +/* [Scenes tab] Hide studio logo/text from scene card */ +.scene-studio-overlay { + display: none; +} + +/* [Scenes tab] Fit more thumbnails on each row */ +.grid { + padding: 0 !important; +} + +/* [Scenes tab] Make the list of tags take up less width */ +.bs-popover-bottom { + max-width: 500px; +} + +/* [Scenes tab] Adjust the mouse over behaviour in wall mode */ +@media (min-width: 576px) { + .wall-item:hover::before, + .wall-item:focus::before { + opacity: 0; + } + + .wall-item:hover .wall-item-container, + .wall-item:focus .wall-item-container { + transform: scale(1.5); + } +} + +/* [Scenes tab] Disable zoom on hover in wall mode */ +.wall-item:hover .wall-item-container, +.wall-item:focus .wall-item-container { + transform: none; +} + +.wall-item::before { + opacity: 0 !important; +} + +/* [Scenes tab] Hide the scene scrubber and max out the player's height */ +.scrubber-wrapper { + display: none; +} + +/* [Scenes Tab] - Hide the truncated text on scene card */ +.TruncatedText.scene-card__description { + display: none; +} + +/* +.performer-image-container { + flex: 0 0 50%; + max-width: 50%; +}*/ + +/* Changing .col-md-8 settings also affects studios and tags display. 50% should be good enough. */ +.col-md-8 { + flex: 0 0 50%; + max-width: 50%; +} + +/* [Performers tab] Move the buttons in the Performer's edit panel to the top instead of bottom (in newer version of Stash, the buttons are already positioned both at top and bottom. */ +form#performer-edit { + display: flex; + flex-direction: column; +} + +#performer-edit > .row { + order: 1; +} + +#performer-edit > .row:last-child { + margin-bottom: 1rem; + order: 0; +} + +/* [Performers tab] Move the tags row in the Performer's edit panel to the second position (just after name). */ +form#performer-edit { + display: flex; + flex-direction: column; +} + +#performer-edit > .row:nth-child(21) { + order: -1; +} + +#performer-edit > .row:first-child { + order: -2; +} + +.scene-tabs .studio-logo { + display: none !important; +} + +/* Custom rating banner */ +body .rating-banner { + background: transparent; + background-position: center; + background-repeat: no-repeat; + background-size: contain; + color: #fff; + display: block; + font-size: .86rem; + font-weight: 400; + height: 0; + left: 0; + letter-spacing: 1px; + line-height: 1.6rem; + margin: 6px 10px; + padding: 10px; + position: absolute; + text-indent: 0; + top: 0; + transform: none; + visibility: hidden; + width: 88px; +} + +body .rating-banner { + background: none; +} + +body .rating-banner::after { + background: var(--color-star); + border-radius: 5px; + color: var(--color-black); + display: inline-block; + font-size: 12px; + font-weight: 800; + height: 30px; + left: 0; + margin-top: 5px; + padding: 5px; + position: absolute; + text-indent: 0; + top: 0; + visibility: visible; + width: 30px; +} + +body .rating-banner.rating-3::after { + content: '3'; +} + +body .rating-banner.rating-4::after { + content: '4'; +} + +body .rating-banner.rating-5::after { + content: '5'; +} + +.wall-item-text { + display: none !important; +} + +body .scene-card-preview-video, +.scene-card-preview.portrait .scene-card-preview-image, +.scene-card-preview.portrait .scene-card-preview-video, +.gallery-card-preview.portrait .scene-card-preview-image, +.gallery-card-preview.portrait .scene-card-preview-video { + object-fit: cover !important; + object-position: center !important; +} + +.bs-popover-bottom > .arrow::after, +.bs-popover-auto[x-placement^="bottom"] > .arrow::after, +.bs-popover-bottom > .arrow::before, +.bs-popover-auto[x-placement^="bottom"] > .arrow::before { + border-bottom-color: var(--color-cod-gray) !important; +} + +[role="tooltip"], +.popover, +.input-control, +.input-control:focus, +.input-control:disabled, +.dropdown-menu, +.dropdown-menu .bg-secondary, +.dropdown-menu .btn-secondary, +.dropdown-menu a, +.scraper-group .btn, +.scraper-group input, +[role="toolbar"] ::-webkit-keygen-select, +[role="toolbar"] input, +[role="toolbar"] .btn, +[role="toolbar"] .btn-secondary { + background-color: var(--color-cod-gray) !important; + border-color: var(--color-cod-gray) !important; + color: var(--color-silver) !important; +} + +.tag-item { + background: var(--color-dark); + border-radius: 8px; + color: var(--color-white); + display: inline-block; + font-size: 14px; + font-weight: 400; + margin-bottom: 8px; + padding: 8px 18px; + text-transform: capitalize; + white-space: nowrap; +} + +.tag-item:hover, +.tag-item:focus { + background: var(--color-dim); +} + +.tag-item:first-of-type { + margin-left: 0; +} + +.row h6 { + margin-top: 10px; +} + +body .nav-tabs .nav-link { + border-bottom: 1px solid transparent !important; + border-radius: 0 !important; + color: var(--color-text); + font-size: 11px; + padding: 8px 15px; +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link, +body .nav-tabs .nav-link.active { + background-color: var(--color-black) !important; + border-bottom: 1px solid var(--color-hubs) !important; + color: var(--color-white) !important; +} + +@media (min-width: 1200px) { + body .navbar-expand-xl .navbar-nav .nav-link { + padding-left: 0; + padding-right: 0; + } +} + +body .text-muted { + color: var(--color-text-dim) !important; +} + +/* Range input */ +input::-webkit-slider-runnable-track, +input::-moz-range-track, +input::-ms-track, +input, +input[type="range"] { + accent-color: var(--color-hubs) !important; + appearance: none; +} + +.custom-control-input:checked ~ .custom-control-label::before { + background-color: var(--color-hubs) !important; + border-color: var(--color-hubs) !important; +} + +input[type="range"] { + appearance: none; + height: 38px; + margin: 10px 0; + width: 100%; +} + +input[type="range"]:focus { + outline: none; +} + +input[type="range"]::-webkit-slider-runnable-track { + background: var(--color-hubs); + border-radius: 5px; + cursor: pointer; + height: 8px; + width: 100%; +} + +input[type="range"]::-webkit-slider-thumb { + appearance: none; + background: #fff; + border-radius: 5px; + cursor: pointer; + height: 20px; + width: 8px; +} + +input[type="range"]:focus::-webkit-slider-runnable-track { + background: var(--color-hubs); +} + +input[type="range"]::-moz-range-track { + background: var(--color-hubs); + border-radius: 5px; + cursor: pointer; + height: 10px; + width: 100%; +} + +input[type="range"]::-moz-range-thumb { + background: #fff; + border-radius: 5px; + cursor: pointer; + height: 30px; + width: 12px; +} + +input[type="range"]::-ms-track { + background: transparent; + border-color: transparent; + color: transparent; + cursor: pointer; + height: 10px; + width: 100%; +} + +input[type="range"]::-ms-fill-lower { + background: var(--color-hubs); + border-radius: 10px; +} + +input[type="range"]::-ms-fill-upper { + background: var(--color-hubs); + border-radius: 10px; +} + +input[type="range"]::-ms-thumb { + background: #fff; + border-radius: 5px; + cursor: pointer; + height: 30px; + margin-top: 1px; + width: 12px; +} + +input[type="range"]:focus::-ms-fill-lower { + background: var(--color-hubs); +} + +input[type="range"]:focus::-ms-fill-upper { + background: var(--color-hubs); +} + +.top-nav .navbar-buttons .btn[title="Help"], +.top-nav .navbar-buttons .btn.donate { + display: none !important; +} + +.stats-element .title { + color: var(--color-white) !important; + font-weight: 700 !important; +} + +div[role="toolbar"] + .d-flex > .tag-item.badge.badge-secondary { + align-items: center; + background: none !important; + bottom: 0; + display: inline-flex; + gap: 10px; +} + +div[role="toolbar"] + .d-flex > .tag-item.badge.badge-secondary button[type="button"] { + align-items: center; + background: none !important; + bottom: 0 !important; + display: inline-flex; + margin: 0 !important; +} + +.recommendation-row.studio-recommendations, +.recommendation-row.movie-recommendations { + display: none !important; +} + +a { + color: var(--color-hubs); +} + +.btn, +.btn-primary, +.btn-secondary { + border-radius: 3px !important; +} + +/* Slick fixes */ +.slick-slider .slick-prev, +.slick-slider .slick-next { + display: none !important; +} + +.recommendations-container { + padding-left: 0 !important; + padding-right: 0 !important; +} + +h2 { + text-transform: unset !important; +} + +.recommendation-row-head { + position: relative; + z-index: 100; +} + +.recommendation-row-head a { + font-size: 14px; +} + +@media (max-width: 1200px) { + .container, + .container-xl, + .container-lg, + .container-md, + .container-sm { + padding-left: 0 !important; + padding-right: 0 !important; + width: unset !important; + } + + .filter-container { + flex-wrap: wrap !important; + margin: 0 !important; + padding-left: 0 !important; + padding-right: 0 !important; + } + + .top-nav .navbar-buttons { + margin-left: unset !important; + margin-right: unset !important; + } + + .fixed-top { + justify-content: space-between !important; + margin-left: unset !important; + margin-right: unset !important; + position: unset !important; + } + + .navbar-dark .navbar-nav .nav-link { + display: block; + flex-basis: 100%; + max-width: 100%; + width: 100%; + } + + .navbar-nav { + gap: 0; + } + + .navbar-dark .navbar-nav .nav-link a { + gap: 14px; + justify-content: flex-start !important; + padding-bottom: 15px !important; + padding-left: 0 !important; + padding-right: 0 !important; + padding-top: 15px !important; + } + + .top-nav .btn .fa-icon { + height: 22px; + width: 22px; + } + + .top-nav .navbar-collapse .navbar-nav { + justify-content: flex-start; + padding-bottom: 0; + padding-top: 1rem; + } + + .pagination .btn { + font-size: 16px !important; + height: 40px !important; + min-width: 40px !important; + } + + .slick-slide .card { + height: unset; + } +} + +[data-rb-event-key="https://opencollective.com/stashapp"] { + display: none !important; +} + +@media (max-width: 576px) { + .top-nav .navbar-collapse .navbar-nav { + padding-bottom: 1rem; + padding-top: 0; + } +} diff --git a/themes/Theme-PornHub/Theme-PornHub.yml b/themes/Theme-PornHub/Theme-PornHub.yml new file mode 100644 index 00000000..f5db01fb --- /dev/null +++ b/themes/Theme-PornHub/Theme-PornHub.yml @@ -0,0 +1,6 @@ +name: Theme - Pornhub +description: PornHub Theme for Stash by ronilaukkarinen +version: 1.0 +ui: + css: + - Theme-PornHub.css From fe9a8715af847bd6763529aff256beb93017ea3e Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 17:53:22 -0500 Subject: [PATCH 30/91] fixes query that aged badly --- plugins/stats/stats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stats/stats.js b/plugins/stats/stats.js index 6aa22c4f..05554933 100644 --- a/plugins/stats/stats.js +++ b/plugins/stats/stats.js @@ -19,8 +19,8 @@ const reqData = { "variables": { "scene_filter": { - "stash_id": { - "value": "", + "stash_id_endpoint": { + "stash_id": "", "modifier": "NOT_NULL" } } From 208c086dc0e360479be41e04e934d1efd4fd5596 Mon Sep 17 00:00:00 2001 From: Scruffy Nerf Date: Mon, 1 Jan 2024 17:58:00 -0500 Subject: [PATCH 31/91] kudos to @maista6969 for solving this better than I did... --- plugins/stats/stats.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/stats/stats.js b/plugins/stats/stats.js index 05554933..65f30d53 100644 --- a/plugins/stats/stats.js +++ b/plugins/stats/stats.js @@ -20,7 +20,6 @@ "variables": { "scene_filter": { "stash_id_endpoint": { - "stash_id": "", "modifier": "NOT_NULL" } } @@ -44,8 +43,7 @@ const reqData = { "variables": { "performer_filter": { - "stash_id": { - "value": "", + "stash_id_endpoint": { "modifier": "NOT_NULL" } } @@ -69,8 +67,7 @@ const reqData = { "variables": { "studio_filter": { - "stash_id": { - "value": "", + "stash_id_endpoint": { "modifier": "NOT_NULL" } } From 47d066ee4d695accb4cf31291d7b1f74f19e1594 Mon Sep 17 00:00:00 2001 From: scruffynerf Date: Tue, 2 Jan 2024 05:24:52 -0500 Subject: [PATCH 32/91] remove obsolete wiki reference I somehow kept missing --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f5511aae..1945069c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains plugin and utility scripts created by the Stash community and hosted on the official GitHub repo. -There is also [a list of third-party plugins on our wiki page](https://github.com/stashapp/stash/wiki/Plugins-&--Scripts). +There is also [a list of third-party plugins in our documentation](https://docs.stashapp.cc/add-ons/third-party-integrations). ## Please note: V24 now uses an installer # We recommend you use that to install (and update) plugins. From 8a4201b61a20e92d910c9a3073017a0f27305362 Mon Sep 17 00:00:00 2001 From: OFP Date: Wed, 3 Jan 2024 16:12:31 -0500 Subject: [PATCH 33/91] Update capitalizeWords to better handle edge cases Converts a filename to title case. Capitalizes all words except for certain conjunctions, prepositions, and articles, unless they are the first or last word of a segment of the filename. Recognizes standard apostrophes, right single quotation marks (U+2019), and left single quotation marks (U+2018) within words. Ignores all caps words and abbreviations, e.g., MILF, BBW, VR, PAWGs. Ignores words with mixed case, e.g., LaSirena69, VRCosplayX, xHamster. Ignores resolutions, e.g., 1080p, 4k. --- plugins/renamerOnUpdate/renamerOnUpdate.py | 69 +++++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/plugins/renamerOnUpdate/renamerOnUpdate.py b/plugins/renamerOnUpdate/renamerOnUpdate.py index 9dab0410..12eae2b8 100644 --- a/plugins/renamerOnUpdate/renamerOnUpdate.py +++ b/plugins/renamerOnUpdate/renamerOnUpdate.py @@ -769,9 +769,72 @@ def makePath(scene_information: dict, query: str) -> str: return r -def capitalizeWords(s: str): - # thanks to BCFC_1982 for it - return re.sub(r"[A-Za-z]+('[A-Za-z]+)?", lambda word: word.group(0).capitalize(), s) +def capitalizeWords(s: str) -> str: + """ + Converts a filename to title case. Capitalizes all words except for certain + conjunctions, prepositions, and articles, unless they are the first or + last word of a segment of the filename. Recognizes standard apostrophes, right + single quotation marks (U+2019), and left single quotation marks (U+2018) within words. + + Ignores all caps words and abbreviations, e.g., MILF, BBW, VR, PAWGs. + Ignores words with mixed case, e.g., LaSirena69, VRCosplayX, xHamster. + Ignores resolutions, e.g., 1080p, 4k. + + Args: + s (str): The string to capitalize. + + Returns: + str: The capitalized string. + + Raises: + ValueError: If the input is not a string. + + About the regex: + The first \b marks the starting word boundary. + [A-Z]? allows for an optional initial uppercase letter. + [a-z\'\u2019\u2018]+ matches one or more lowercase letters, apostrophes, right single quotation marks, or left single quotation marks. + If a word contains multiple uppercase letters, it does not match. + The final \b marks the ending word boundary, ensuring the expression matches whole words. + """ + if not isinstance(s, str): + raise ValueError("Input must be a string.") + + # Function to capitalize words based on their position and value. + def process_word(match): + word = match.group(0) + preceding_char, following_char = None, None + + # List of words to avoid capitalizing if found between other words. + exceptions = {"and", "of", "the"} + + # Find the nearest non-space character before the current word + if match.start() > 0: + for i in range(match.start() - 1, -1, -1): + if not match.string[i].isspace(): + preceding_char = match.string[i] + break + + # Find the nearest non-space character after the current word + if match.end() < len(s): + for i in range(match.end(), len(s)): + if not match.string[i].isspace(): + following_char = match.string[i] + break + + # Determine capitalization based on the position and the exception rules + if ( + match.start() == 0 + or match.end() == len(s) + or word.lower() not in exceptions + or (preceding_char and not preceding_char.isalnum()) + or (following_char and not following_char.isalnum()) + ): + return word.capitalize() + else: + return word.lower() + + # Apply the regex pattern and the process_word function. + return re.sub(r"\b[A-Z]?[a-z\'\u2019\u2018]+\b", process_word, s) def create_new_filename(scene_info: dict, template: str): From 71c96b0428350fe61145bb09ec714273f04ca4f4 Mon Sep 17 00:00:00 2001 From: CJ <72030708+cj12312021@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:31:13 -0600 Subject: [PATCH 34/91] Fixes some broken services from this library --- .../stashUserscriptLibrary.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 9cac4202..7670e3b1 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -1,5 +1,28 @@ const stashListener = new EventTarget(); +const { + fetch: originalFetch +} = window; + +window.fetch = async (...args) => { + let [resource, config] = args; + // request interceptor here + const response = await originalFetch(resource, config); + // response interceptor here + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1 && resource.endsWith('/graphql')) { + try { + const data = await response.clone().json(); + stashListener.dispatchEvent(new CustomEvent('response', { + 'detail': data + })); + } catch (e) { + + } + } + return response; +}; + class Logger { constructor(enabled) { this.enabled = enabled; From bfa6e275b7f6cc06ec90b7bcebc8fe92db29d0b9 Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Thu, 11 Jan 2024 20:37:40 +1030 Subject: [PATCH 35/91] Adding Movie sync support and extra urls from timestamp.trade If someone has linked a scene to a movie and submitted the data there can be movie information in timestamp.trade. If enabled a movie will be created and the scene number linked on save. If the extra urls have been submitted to timestamp.trade add them to the scene. I am hoping to crowd source links to funscripts for example. --- plugins/timestampTrade/timestampTrade.py | 150 +++++++++++++++++----- plugins/timestampTrade/timestampTrade.yml | 16 ++- 2 files changed, 130 insertions(+), 36 deletions(-) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index c8c10cee..75d738fc 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -42,38 +42,100 @@ def processScene(s): mp.import_scene_markers(stash, markers, s['id'], 15) else: log.debug('api returned no markers for scene: ' + s['title']) - if 'galleries' in md: - log.info(md['galleries']) - skip_sync_tag_id = stash.find_tag('[Timestamp: Skip Sync]', create=True).get("id") - for g in md['galleries']: - for f in g['files']: - res = stash.find_galleries(f={"checksum": {"value": f['md5'], "modifier": "EQUALS"}, - "tags": {"depth": 0, "excludes": [skip_sync_tag_id], - "modifier": "INCLUDES_ALL", "value": []}}) - for gal in res: - # log.debug('Gallery=%s' %(gal,)) - gallery = { - 'id': gal['id'], - 'title': gal['title'], - 'urls': gal['urls'], - 'date': gal['date'], - 'rating100': gal['rating100'], - 'studio_id': gal['studio']['id'], - 'performer_ids': [x['id'] for x in gal['performers']], - 'tag_ids': [x['id'] for x in gal['tags']], - 'scene_ids': [x['id'] for x in gal['scenes']], - 'details': gal['details'] - } - if len(gal['urls']) == 0: - log.debug('no urls on gallery, needs new metadata') - gallery['urls'].extend([x['url'] for x in g['urls']]) - - if s['id'] not in gallery['scene_ids']: - log.debug('attaching scene %s to gallery %s ' % (s['id'], gallery['id'],)) - gallery['scene_ids'].append(s['id']) - log.info('updating gallery: %s' % (gal['id'],)) - stash.update_gallery(gallery_data=gallery) - + if settings['createGalleryFromScene']: + if 'galleries' in md: + log.debug('galleries: %s' % (md['galleries'],)) + skip_sync_tag_id = stash.find_tag('[Timestamp: Skip Sync]', create=True).get("id") + for g in md['galleries']: + for f in g['files']: + res = stash.find_galleries(f={"checksum": {"value": f['md5'], "modifier": "EQUALS"}, + "tags": {"depth": 0, "excludes": [skip_sync_tag_id], + "modifier": "INCLUDES_ALL", "value": []}}) + for gal in res: + # log.debug('Gallery=%s' %(gal,)) + needs_update=False + gallery = { + 'id': gal['id'], + 'title': gal['title'], + 'urls': gal['urls'], + 'date': gal['date'], + 'rating100': gal['rating100'], + 'studio_id': gal['studio']['id'], + 'performer_ids': [x['id'] for x in gal['performers']], + 'tag_ids': [x['id'] for x in gal['tags']], + 'scene_ids': [x['id'] for x in gal['scenes']], + 'details': gal['details'] + } + if len(gal['urls']) == 0: + log.debug('no urls on gallery, needs new metadata') + gallery['urls'].extend([x['url'] for x in g['urls']]) + needs_update=True + + if s['id'] not in gallery['scene_ids']: + log.debug('attaching scene %s to gallery %s ' % (s['id'], gallery['id'],)) + gallery['scene_ids'].append(s['id']) + needs_update=True + if needs_update: + log.info('updating gallery: %s' % (gal['id'],)) + stash.update_gallery(gallery_data=gallery) + + new_scene={ + 'id': s['id'], + } + needs_update=False + + if settings['createMovieFromScene']: + if 'movies' in md: + movies_to_add = [] + for m in md['movies']: + log.debug('movie: %s' % (m,)) + log.debug('scene: %s' % (s,)) + movies=[] + for u in m['urls']: + sm=stash.find_movies(f= {"url": {"modifier": "EQUALS","value": u['url']}}) + log.debug('sm: %s' % (sm,)) + movies.extend(sm) + if len(movies) ==0: + for u in m['urls']: + movie_scrape=stash.scrape_movie_url(u['url']) + log.debug('move scrape: %s' % (movie_scrape,)) + new_movie={ + 'name':movie_scrape['name'], + 'aliases':movie_scrape['aliases'], + 'date':movie_scrape['date'], + 'rating100': movie_scrape['rating'], + 'director': movie_scrape['director'], + 'synopsis':movie_scrape['synopsis'], + 'url':movie_scrape['url'], + 'front_image':movie_scrape['front_image'], + 'back_image':movie_scrape['back_image'] + } + if not movie_scrape['url']: + new_movie['url']=u['url'] + if movie_scrape['studio']: + new_movie['studio_id']=movie_scrape['studio']['stored_id'] + log.debug('new movie: %s' % (new_movie,)) + nm=stash.create_movie(new_movie) + movies.append(nm) + movies_to_add.extend([{'movie_id':x['id'],'scene_index':m['scene_index']} for x in movies]) + if len(movies_to_add) >0: + new_scene['movies'] =[] + for m in movies_to_add: + if m['movie_id'] not in [x['movie']['id'] for x in s['movies']]: + new_scene['movies'].append(m) + needs_update=True + + if settings['extraUrls']: + extra_urls=s['urls'] + for url in md['urls']: + if url['url'] not in s['urls']: + extra_urls.append(url['url']) + needs_update=True + if needs_update: + new_scene['urls']=extra_urls + if needs_update: + log.debug('new scene update: %s' % (new_scene,)) + stash.update_scene(new_scene) except json.decoder.JSONDecodeError: @@ -94,7 +156,8 @@ def processAll(): processScene(s) i=i+1 log.progress((i/count)) - time.sleep(1) + time.sleep(2) + def submitScene(): scene_fgmt = """title @@ -288,8 +351,15 @@ def processGalleries(): for gal in galleries: processGallery(gal) def processGallery(gallery): + process=False # ignore galleries with a url if len(gallery['urls']) ==0: + process=True + # Process the gallery if it has the [Timestamp: Tag Gallery] tag + tag_gallery_tag_id = stash.find_tag('[Timestamp: Tag Gallery]', create=True).get("id") + if tag_gallery_tag_id in gallery['tags']: + process=True + if process: for f in gallery['files']: for fp in f['fingerprints']: if fp['type']=='md5': @@ -360,8 +430,6 @@ def processGallery(gallery): break log.debug(new_gallery) - - stash.update_gallery(gallery_data=new_gallery) time.sleep(1) @@ -384,6 +452,17 @@ def getImages(gallery_id): FRAGMENT_SERVER = json_input["server_connection"] stash = StashInterface(FRAGMENT_SERVER) +config=stash.get_configuration()['plugins'] +settings={ + 'createGalleryFromScene':True, + 'createMovieFromScene':True, + } +if 'timestampTrade' in config: + settings.update(config['timestampTrade']) +log.info('config: %s ' % (settings,)) + + + if 'mode' in json_input['args']: PLUGIN_ARGS = json_input['args']["mode"] if 'submitScene' in PLUGIN_ARGS: @@ -404,3 +483,4 @@ def getImages(gallery_id): gallery=stash.find_gallery(id) processGallery(gallery) + diff --git a/plugins/timestampTrade/timestampTrade.yml b/plugins/timestampTrade/timestampTrade.yml index 2a693249..aa1f08da 100644 --- a/plugins/timestampTrade/timestampTrade.yml +++ b/plugins/timestampTrade/timestampTrade.yml @@ -1,11 +1,25 @@ name: Timestamp Trade description: Sync Markers with timestamp.trade, a new database for sharing markers. -version: 0.3 +version: 0.4 url: https://github.com/stashapp/CommunityScripts/ exec: - python - "{pluginDir}/timestampTrade.py" interface: raw +settings: + createGalleryFromScene: + displayName: Create Gallery from scene + description: If there is a gallery linked to the scene in the timestamp.trade database and a gallery with the same file hash is found create a gallery inside stash + type: BOOLEAN + createMovieFromScene: + displayName: Create Movie from scene + description: If there is a Movie linked to the scene in the timestamp.trade database automatically create a movie in stash with that info + type: BOOLEAN + extraUrls: + displayName: Add extra urls if they exist on timestamp.trade + description: Extra urls can be submitted to timestamp.trade, add them to the scene if they exist + type: BOOLEAN + hooks: - name: Add Marker to Scene description: Makes Markers checking against timestamp.trade From 8243d14caecdfe17b9c383446d675649ff981078 Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Thu, 11 Jan 2024 21:07:02 +1030 Subject: [PATCH 36/91] Adding miscTags plugin, this plugin parses the file name and adds vr related tags for stash-vr-companion. --- plugins/miscTags/miscTags.py | 154 ++++++++++++++++++++++++++++++++++ plugins/miscTags/miscTags.yml | 31 +++++++ 2 files changed, 185 insertions(+) create mode 100644 plugins/miscTags/miscTags.py create mode 100644 plugins/miscTags/miscTags.yml diff --git a/plugins/miscTags/miscTags.py b/plugins/miscTags/miscTags.py new file mode 100644 index 00000000..3f47cf50 --- /dev/null +++ b/plugins/miscTags/miscTags.py @@ -0,0 +1,154 @@ +import stashapi.log as log +from stashapi.stashapp import StashInterface +import sys +import json +import time + + +per_page = 100 +skip_tag='[MiscTags: Skip]' + +# Defaults if nothing has changed in the stash ui +settings={ + 'addStashVrCompanionTags':False, + 'addVrTags':False, + 'flatStudio':'' + } + +VRCTags={ + 'flat':{'VRCTags':['FLAT'],'projTags':[]}, + '180_mono':{'VRCTags':['DOME','MONO'],'projTags':['180°']}, + '360_mono':{'VRCTags':['SPHERE','MONO'],'projTags':['360°']}, + '180_sbs':{'VRCTags':['DOME','SBS'],'projTags':['180°']}, + 'LR_180':{'VRCTags':['DOME','SBS'],'projTags':['180°']}, + '180_lr': {'VRCTags':['DOME', 'SBS'],'projTags':['180°']}, + '180_3dh_lr': {'VRCTags': ['DOME', 'SBS'], 'projTags': ['180°']}, + '360_tb':{'VRCTags':['SPHERE','SBS'],'projTags':['360°']}, + 'mkx200':{'VRCTags':['MKX200','FISHEYE','SBS'],'projTags':['220°']}, + 'mkx220':{'VRCTags':['MKX220','FISHEYE','SBS'],'projTags':['220°']}, + 'vrca220': {'VRCTags':['VRCA220', 'FISHEYE', 'SBS'],'projTags':['220°']}, + 'rf52':{'VRCTags':['RF52','FISHEYE', 'SBS'],'projTags':['190°']}, + 'fisheye190': {'VRCTags':['RF52', 'FISHEYE', 'SBS'],'projTags':['190°']}, + 'passthrough':{'VRCTags':[],'projTags':['Augmented Reality']}, + '8k': {'VRCTags': [], 'projTags': ['8K']}, + '7k': {'VRCTags': [], 'projTags': ['7K']}, + '6k': {'VRCTags': [], 'projTags': ['6K']}, + '5k': {'VRCTags': [], 'projTags': ['5K']}, + } +tags_cache={} + +def processScene(scene): + log.debug('processing scene: %s' % (scene['id'],)) + # if the scene has [MiscTags: Skip] then skip it + if skip_tag not in tags_cache: + tags_cache[skip_tag]=stash.find_tag(skip_tag, create=True).get("id") + if tags_cache[skip_tag] not in [x['id'] for x in scene['tags']]: + tags=[] + if settings['addStashVrCompanionTags']: + processStashVRCompanionTags(scene,tags) + log.debug(tags) + if settings['addVrTags']: + processVRTags(scene,tags) + if len(settings['flatStudio']) > 0: + processFlatStudio(scene,tags) + + if len(tags) > 0: + log.debug('processing scene %s, checking if tags need to be added: %s' % (scene['title'],tags,)) + # start with the existing tag id's, then look up the new tag id's and create if needed + new_scene = {'id': scene['id'], 'tag_ids': [x['id'] for x in scene['tags']]} + update=False + for t in tags: + if t not in tags_cache: + tags_cache[t]=stash.find_tag(t, create=True).get("id") + update=True + if tags_cache[t] not in new_scene['tag_ids']: + new_scene['tag_ids'].append(tags_cache[t]) + update=True + if update: + log.info('Adding tags to scene: %s, tags: %s' % (scene['title'], tags,)) + stash.update_scene(new_scene) + else: + log.debug('no update') + else: + log.debug('skipping scene') + + +def processStashVRCompanionTags(scene,tags): + found=False + for f in scene['files']: + for k,v in VRCTags.items(): + if k in f['basename'].lower(): + tags.extend(v['VRCTags']) + found=True + if found: + tags.append('export_deovr') + return None + +def processVRTags(scene,tags): + found=False + for f in scene['files']: + for k,v in VRCTags.items(): + if k in f['basename'].lower(): + tags.extend(v['projTags']) + found=True + if found: + if 'vrTag' in stash.get_configuration()['ui']: + vr_tag=stash.get_configuration()['ui']['vrTag'] + if vr_tag: + tags.append(vr_tag) + else: + tags.append('VR') + else: + tags.append('VR') + return None + +def processFlatStudio(scene,tags): + log.debug(scene) + if scene['studio']: + if scene['studio']['id'] in [x.strip() for x in settings['flatStudio'].split(',')]: + if 'export_deovr' not in tags: + tags.append('export_deovr') + + tags.append('FLAT') + log.debug(tags) + +def processScenes(): + log.info('Getting scene count') + if skip_tag not in tags_cache: + tags_cache[skip_tag]=stash.find_tag(skip_tag, create=True).get("id") + count=stash.find_scenes(f={"tags":{"depth":0,"excludes":[tags_cache[skip_tag]],"modifier":"INCLUDES_ALL","value":[]}},filter={"per_page": 1},get_count=True)[0] + log.info(str(count)+' scenes to process.') + i=0 + for r in range(1,int(count/per_page)+2): + log.info('adding tags to scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) + scenes=stash.find_scenes(f={"tags":{"depth":0,"excludes":[tags_cache[skip_tag]],"modifier":"INCLUDES_ALL","value":[]}},filter={"page":r,"per_page": per_page}) + for s in scenes: + processScene(s) + i=i+1 + log.progress((i/count)) + time.sleep(1) + + +json_input = json.loads(sys.stdin.read()) + +FRAGMENT_SERVER = json_input["server_connection"] +stash = StashInterface(FRAGMENT_SERVER) +config=stash.get_configuration()['plugins'] +if 'misc-tags' in config: + settings.update(config['misc-tags']) +log.info('config: %s ' % (settings,)) + +if 'mode' in json_input['args']: + PLUGIN_ARGS = json_input['args']["mode"] + if 'processScenes' in PLUGIN_ARGS: + processScenes() + elif 'test' in PLUGIN_ARGS: + log.debug(stash.get_configuration()['ui']['vrTag']) + + + +elif 'hookContext' in json_input['args']: + id=json_input['args']['hookContext']['id'] + if json_input['args']['hookContext']['type']=='Scene.Update.Post': + scene=stash.find_scene(id) + processScene(scene) \ No newline at end of file diff --git a/plugins/miscTags/miscTags.yml b/plugins/miscTags/miscTags.yml new file mode 100644 index 00000000..6e7da599 --- /dev/null +++ b/plugins/miscTags/miscTags.yml @@ -0,0 +1,31 @@ +name: Misc Tags +description: Add extra tags for VR and other ues +version: 0.2 +url: https://github.com/stashapp/CommunityScripts/ +exec: + - python + - "{pluginDir}/miscTags.py" +interface: raw +settings: + addStashVRCompanionTags: + displayName: Add tags for stash-vr-companion + description: Add tags useful for stash-vr-companion, export_deovr, DOME, SBS, VR based on filename string + type: BOOLEAN + addVRTags: + displayName: Add VR related tags + description: Add projection based tags 180°, 200°, VR + type: BOOLEAN + flatStudio: + displayName: 2d Studio for stash-vr-companion + description: Comma seperated list of studio number in your instance to tag with export_devor,FLAT for use with stash-vr-companion + type: STRING +hooks: + - name: process Scene + description: Makes Markers checking against timestamp.trade + triggeredBy: + - Scene.Update.Post +tasks: + - name: 'process all' + description: Submit markers to timestamp.trade + defaultArgs: + mode: processScenes From 1accf6ec9c3f6c63ba2d056b837fd3da879aded6 Mon Sep 17 00:00:00 2001 From: Tweeticoats <60335703+Tweeticoats@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:28:25 +1030 Subject: [PATCH 37/91] bugfix, timestampTrade.py set a default config value Bugfix, set a default value for the config option as it does not exist in the plugin config until the value has been set. --- plugins/timestampTrade/timestampTrade.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index 75d738fc..352eec1e 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -456,6 +456,7 @@ def getImages(gallery_id): settings={ 'createGalleryFromScene':True, 'createMovieFromScene':True, + 'extraUrls':True, } if 'timestampTrade' in config: settings.update(config['timestampTrade']) From 6d37a42dc0cdb17b187656cdc1240a9ed578b81f Mon Sep 17 00:00:00 2001 From: raghavan Date: Wed, 17 Jan 2024 03:52:41 +0530 Subject: [PATCH 38/91] improve page listeners --- .../stashUserscriptLibrary.js | 304 ++++++++++++++---- 1 file changed, 237 insertions(+), 67 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 7670e3b1..e2824399 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -36,19 +36,24 @@ class Logger { class Stash extends EventTarget { constructor({ - pageUrlCheckInterval = 1, + pageUrlCheckInterval = 100, + detectReRenders = false, // detects if Root element is re-renders. eg: When this is true and you are in scenes page and click the scenes nav tab the url wont change but the elements are re-rendered, So you can listen to this and alter the root elements once again logging = false } = {}) { super(); this.log = new Logger(logging); this._pageUrlCheckInterval = pageUrlCheckInterval; + this._detectReRenders = detectReRenders; this.fireOnHashChangesToo = true; this.pageURLCheckTimer = setInterval(() => { - // Loop every 500ms - if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash)) { + // Loop every 100 ms + if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { this.lastPathStr = location.pathname; this.lastQueryStr = location.search; this.lastHashStr = location.hash; + if (this._detectReRenders) document.querySelector(".main > div").setAttribute("stashUserscriptLibrary", ""); + this.log.debug('[Navigation] Page Changed'); + this.dispatchEvent(new Event('page')); this.gmMain(); } }, this._pageUrlCheckInterval); @@ -377,7 +382,83 @@ class Stash extends EventTarget { get serverUrl() { return window.location.origin; } - gmMain() { + async waitForElement(selector, timeout = null, disconnectOnPageChange = false) { + return new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)) + } + + const observer = new MutationObserver(async () => { + if (document.querySelector(selector)) { + resolve(document.querySelector(selector)) + observer.disconnect() + } else { + if (timeout) { + async function timeOver() { + return new Promise((resolve) => { + setTimeout(() => { + observer.disconnect() + resolve(false) + }, timeout) + }) + } + resolve(await timeOver()) + } + } + }) + + observer.observe(document.body, { + childList: true, + subtree: true, + }) + + if (disconnectOnPageChange) { + function disconnect() { + observer.disconnect() + this.removeEventListener("page", disconnect) + } + this.addEventListener("page", disconnect) + } + }) + } + async waitForElementDeath(selector, disconnectOnPageChange = false) { + return new Promise((resolve) => { + const observer = new MutationObserver(async () => { + if (!document.querySelector(selector)) { + resolve(true) + observer.disconnect() + } + }) + + observer.observe(document.body, { + childList: true, + subtree: true, + }) + + if (disconnectOnPageChange) { + function disconnect() { + observer.disconnect() + this.removeEventListener("page", disconnect) + } + this.addEventListener("page", disconnect) + } + }) + } + async _listenForNonPageChanges(selector, event, eventMessage, isRecursive, reRunGmMain = false){ + if (isRecursive) return + if (await this.waitForElement(selector, null, true)) { + this.log.debug("[Navigation] " + eventMessage); + this.dispatchEvent(new Event(event)); + if (await this.waitForElementDeath(selector, true)) { + if (this.lastPathStr === location.pathname && !reRunGmMain) { + this._listenForNonPageChanges(selector, event, eventMessage) + } else if (this.lastPathStr === location.pathname && reRunGmMain) { + this.gmMain(true) + } + } + } + } + gmMain(isRecursive) { const location = window.location; this.log.debug(URL, window.location); @@ -386,26 +467,43 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Wall-Markers Page'); this.dispatchEvent(new Event('page:markers')); } +// create scene page + else if (this.matchUrl(location, /\/scenes\/new/)) { + this.log.debug('[Navigation] Create Scene Page'); + this.dispatchEvent(new Event('page:scene:new')); + } // scene page else if (this.matchUrl(location, /\/scenes\/\d+/)) { this.log.debug('[Navigation] Scene Page'); this.dispatchEvent(new Event('page:scene')); + + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-details-panel']", "page:scene:details", "Scene Page - Details", isRecursive); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-queue-panel']", "page:scene:queue", "Scene Page - Queue", isRecursive); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-markers-panel']", "page:scene:markers", "Scene Page - Markers", isRecursive); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-video-filter-panel']", "page:scene:filters", "Scene Page - Filters", isRecursive); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-file-info-panel']", "page:scene:file-info", "Scene Page - File Info", isRecursive); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-edit-panel']", "page:scene:edit", "Scene Page - Edit", isRecursive); } // scenes wall else if (this.matchUrl(location, /\/scenes\?/)) { + this.log.debug('[Navigation] Wall-Scenes Page'); this.processTagger(); this.dispatchEvent(new Event('page:scenes')); } - // images wall - if (this.matchUrl(location, /\/images\?/)) { - this.log.debug('[Navigation] Wall-Images Page'); - this.dispatchEvent(new Event('page:images')); - } // image page - if (this.matchUrl(location, /\/images\/\d+/)) { + else if (this.matchUrl(location, /\/images\/\d+/)) { this.log.debug('[Navigation] Image Page'); this.dispatchEvent(new Event('page:image')); + + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-details-panel']", "page:image:details", "Image Page - Details"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-file-info-panel']", "page:image:details", "Image Page - File Info"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-edit-panel']", "page:image:details", "Image Page - Edit"); + } + // images wall + else if (this.matchUrl(location, /\/images\?/)) { + this.log.debug('[Navigation] Wall-Images Page'); + this.dispatchEvent(new Event('page:images')); } // movie scenes page @@ -425,66 +523,72 @@ class Stash extends EventTarget { this.dispatchEvent(new Event('page:movies')); } - // galleries wall - if (this.matchUrl(location, /\/galleries\?/)) { - this.log.debug('[Navigation] Wall-Galleries Page'); - this.dispatchEvent(new Event('page:galleries')); + // gallery add page + else if (this.matchUrl(location, /\/galleries\/\d+\/add/)) { + this.log.debug('[Navigation] Gallery Add Page'); + this.dispatchEvent(new Event('page:gallery:add')); + } + // create gallery page + else if (this.matchUrl(location, /\/galleries\/new/)) { + this.log.debug('[Navigation] Create Gallery Page'); + this.dispatchEvent(new Event('page:gallery:new')); } // gallery page - if (this.matchUrl(location, /\/galleries\/\d+/)) { + else if (this.matchUrl(location, /\/galleries\/\d+/)) { this.log.debug('[Navigation] Gallery Page'); this.dispatchEvent(new Event('page:gallery')); - } + this.log.debug('[Navigation] Gallery Page - Images'); + this.dispatchEvent(new Event('page:gallery:images')); - // performer scenes page - if (this.matchUrl(location, /\/performers\/\d+\?/)) { - this.log.debug('[Navigation] Performer Page - Scenes'); - this.processTagger(); - this.dispatchEvent(new Event('page:performer:scenes')); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-details-panel']", "page:gallery:details", "Gallery Page - Details"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-file-info-panel']", "page:gallery:file-info", "Gallery Page - File Info"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-chapter-panel']", "page:gallery:chapters", "Gallery Page - Chapters"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-edit-panel']", "page:gallery:edit", "Gallery Page - Edit"); } - // performer appearswith page - if (this.matchUrl(location, /\/performers\/\d+\/appearswith/)) { - this.log.debug('[Navigation] Performer Page - Appears With'); - this.processTagger(); - this.dispatchEvent(new Event('page:performer:performers')); + // galleries wall + else if (this.matchUrl(location, /\/galleries\?/)) { + this.log.debug('[Navigation] Wall-Galleries Page'); + this.dispatchEvent(new Event('page:galleries')); } + // performer galleries page else if (this.matchUrl(location, /\/performers\/\d+\/galleries/)) { this.log.debug('[Navigation] Performer Page - Galleries'); this.dispatchEvent(new Event('page:performer:galleries')); } + // performer images page + else if (this.matchUrl(location, /\/performers\/\d+\/images/)) { + this.log.debug('[Navigation] Performer Page - Images'); + this.dispatchEvent(new Event('page:performer:images')); + } + // performer movies page + else if (this.matchUrl(location, /\/performers\/\d+\/movies/)) { + this.log.debug('[Navigation] Performer Page - Movies'); + this.dispatchEvent(new Event('page:performer:movies')); + } + // performer appearswith page + else if (this.matchUrl(location, /\/performers\/\d+\/appearswith/)) { + this.log.debug('[Navigation] Performer Page - Appears With'); + this.processTagger(); + this.dispatchEvent(new Event('page:performer:performers')); + } // performer movies page else if (this.matchUrl(location, /\/performers\/\d+\/movies/)) { this.log.debug('[Navigation] Performer Page - Movies'); this.dispatchEvent(new Event('page:performer:movies')); } - // performer page - else if (this.matchUrl(location, /\/performers\//)) { - this.log.debug('[Navigation] Performers Page'); - this.dispatchEvent(new Event('page:performer')); - this.dispatchEvent(new Event('page:performer:details')); + // create performer page + else if (this.matchUrl(location, /\/performers\/new/)) { + this.log.debug('[Navigation] Create Performer Page'); + this.dispatchEvent(new Event('page:performer:new')); + } + // performer scenes page + else if (this.matchUrl(location, /\/performers\/\d+\?/)) { + this.log.debug('[Navigation] Performer Page - Scenes'); + this.processTagger(); + this.dispatchEvent(new Event('page:performer:scenes')); - waitForElementClass('performer-tabs', (className, targetNode) => { - const observerOptions = { - childList: true - } - const observer = new MutationObserver(mutations => { - let isPerformerEdit = false; - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node.id === 'performer-edit') { - isPerformerEdit = true; - } - }); - }); - if (isPerformerEdit) { - this.dispatchEvent(new Event('page:performer:edit')); - } else { - this.dispatchEvent(new Event('page:performer:details')); - } - }); - observer.observe(targetNode[0], observerOptions); - }); + this._listenForNonPageChanges("#performer-edit", "page:performer:edit", "Performer Page - Edit", false, true); } // performers wall else if (this.matchUrl(location, /\/performers\?/)) { @@ -493,7 +597,7 @@ class Stash extends EventTarget { } // studio galleries page - if (this.matchUrl(location, /\/studios\/\d+\/galleries/)) { + else if (this.matchUrl(location, /\/studios\/\d+\/galleries/)) { this.log.debug('[Navigation] Studio Page - Galleries'); this.dispatchEvent(new Event('page:studio:galleries')); } @@ -517,11 +621,18 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Studio Page - Child Studios'); this.dispatchEvent(new Event('page:studio:childstudios')); } + // create studio page + else if (this.matchUrl(location, /\/studios\/new/)) { + this.log.debug('[Navigation] Create Studio Page'); + this.dispatchEvent(new Event('page:studio:new')); + } // studio scenes page else if (this.matchUrl(location, /\/studios\/\d+\?/)) { this.log.debug('[Navigation] Studio Page - Scenes'); this.processTagger(); this.dispatchEvent(new Event('page:studio:scenes')); + + this._listenForNonPageChanges("#studio-edit", "page:studio:edit", "Studio Page - Edit", false, true); } // studio page else if (this.matchUrl(location, /\/studios\/\d+/)) { @@ -535,7 +646,7 @@ class Stash extends EventTarget { } // tag galleries page - if (this.matchUrl(location, /\/tags\/\d+\/galleries/)) { + else if (this.matchUrl(location, /\/tags\/\d+\/galleries/)) { this.log.debug('[Navigation] Tag Page - Galleries'); this.dispatchEvent(new Event('page:tag:galleries')); } @@ -554,22 +665,24 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Tag Page - Performers'); this.dispatchEvent(new Event('page:tag:performers')); } + // create tag page + else if (this.matchUrl(location, /\/tags\/new/)) { + this.log.debug('[Navigation] Create Tag Page'); + this.dispatchEvent(new Event('page:tag:new')); + } // tag scenes page else if (this.matchUrl(location, /\/tags\/\d+\?/)) { this.log.debug('[Navigation] Tag Page - Scenes'); this.processTagger(); this.dispatchEvent(new Event('page:tag:scenes')); + + this._listenForNonPageChanges("#tag-edit", "page:tag:edit", "Tag Page - Edit", false, true); } // tag page else if (this.matchUrl(location, /\/tags\/\d+/)) { this.log.debug('[Navigation] Tag Page'); this.dispatchEvent(new Event('page:tag')); } - // tags any page - if (this.matchUrl(location, /\/tags\/\d+/)) { - this.log.debug('[Navigation] Tag Page - Any'); - this.dispatchEvent(new Event('page:tag:any')); - } // tags wall else if (this.matchUrl(location, /\/tags\?/)) { this.log.debug('[Navigation] Wall-Tags Page'); @@ -577,29 +690,86 @@ class Stash extends EventTarget { } // settings page tasks tab - if (this.matchUrl(location, /\/settings\?tab=tasks/)) { + else if (this.matchUrl(location, /\/settings\?tab=tasks/)) { this.log.debug('[Navigation] Settings Page Tasks Tab'); this.dispatchEvent(new Event('page:settings:tasks')); this.hidePluginTasks(); } + // settings page library tab + else if (this.matchUrl(location, /\/settings\?tab=library/)) { + this.log.debug('[Navigation] Settings Page Library Tab'); + this.dispatchEvent(new Event('page:settings:library')); + } + // settings page interface tab + else if (this.matchUrl(location, /\/settings\?tab=interface/)) { + this.log.debug('[Navigation] Settings Page Interface Tab'); + this.dispatchEvent(new Event('page:settings:interface')); + } + // settings page security tab + else if (this.matchUrl(location, /\/settings\?tab=security/)) { + this.log.debug('[Navigation] Settings Page Security Tab'); + this.dispatchEvent(new Event('page:settings:security')); + } + // settings page metadata providers tab + else if (this.matchUrl(location, /\/settings\?tab=metadata-providers/)) { + this.log.debug('[Navigation] Settings Page Metadata Providers Tab'); + this.dispatchEvent(new Event('page:settings:metadata-providers')); + } + // settings page services tab + else if (this.matchUrl(location, /\/settings\?tab=services/)) { + this.log.debug('[Navigation] Settings Page Services Tab'); + this.dispatchEvent(new Event('page:settings:services')); + } // settings page system tab else if (this.matchUrl(location, /\/settings\?tab=system/)) { this.log.debug('[Navigation] Settings Page System Tab'); this.createSettings(); this.dispatchEvent(new Event('page:settings:system')); } - // settings page (defaults to tasks tab) - else if (this.matchUrl(location, /\/settings/)) { - this.log.debug('[Navigation] Settings Page Tasks Tab'); - this.dispatchEvent(new Event('page:settings:tasks')); - this.hidePluginTasks(); + // settings page plugins tab + else if (this.matchUrl(location, /\/settings\?tab=plugins/)) { + this.log.debug('[Navigation] Settings Page Plugins Tab'); + this.dispatchEvent(new Event('page:settings:plugins')); + } + // settings page logs tab + else if (this.matchUrl(location, /\/settings\?tab=logs/)) { + this.log.debug('[Navigation] Settings Page Logs Tab'); + this.dispatchEvent(new Event('page:settings:logs')); + } + // settings page tools tab + else if (this.matchUrl(location, /\/settings\?tab=tools/)) { + this.log.debug('[Navigation] Settings Page Tools Tab'); + this.dispatchEvent(new Event('page:settings:tools')); + } + // settings page changelog tab + else if (this.matchUrl(location, /\/settings\?tab=changelog/)) { + this.log.debug('[Navigation] Settings Page Changelog Tab'); + this.dispatchEvent(new Event('page:settings:changelog')); + } + // settings page about tab + else if (this.matchUrl(location, /\/settings\?tab=about/)) { + this.log.debug('[Navigation] Settings Page About Tab'); + this.dispatchEvent(new Event('page:settings:about')); } // stats page - if (this.matchUrl(location, /\/stats/)) { + else if (this.matchUrl(location, /\/stats/)) { this.log.debug('[Navigation] Stats Page'); this.dispatchEvent(new Event('page:stats')); } + + // home page + else if (this.matchUrl(location, /\/$/)) { + this.log.debug('[Navigation] Home Page'); + this.dispatchEvent(new Event('page:home')); + + this._listenForNonPageChanges(".recommendations-container-edit", "page:home:edit", "Home Page - Edit", false, true); + } + } + addEventListeners(events, callback) { + events.forEach((event) => { + this.addEventListener(event, callback); + }); } hidePluginTasks() { // hide userscript functions plugin tasks @@ -958,7 +1128,7 @@ class Stash extends EventTarget { } } -stash = new Stash(); +window.stash = new Stash(); function waitForElementClass(elementId, callBack, time) { time = (typeof time !== 'undefined') ? time : 100; From d0bd78125163aeae574961655bd519fc17b1c803 Mon Sep 17 00:00:00 2001 From: raghavan Date: Wed, 17 Jan 2024 04:07:43 +0530 Subject: [PATCH 39/91] touch up --- .../stashUserscriptLibrary/stashUserscriptLibrary.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index e2824399..54057204 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -37,7 +37,7 @@ class Logger { class Stash extends EventTarget { constructor({ pageUrlCheckInterval = 100, - detectReRenders = false, // detects if Root element is re-renders. eg: When this is true and you are in scenes page and click the scenes nav tab the url wont change but the elements are re-rendered, So you can listen to this and alter the root elements once again + detectReRenders = false, // detects if .main element is re-rendered. eg: When you are in scenes page and clicking the scenes nav tab the url wont change but the elements are re-rendered, So with this you can listen and alter the elements inside the .main node logging = false } = {}) { super(); @@ -444,7 +444,7 @@ class Stash extends EventTarget { } }) } - async _listenForNonPageChanges(selector, event, eventMessage, isRecursive, reRunGmMain = false){ + async _listenForNonPageChanges(selector, event, eventMessage, isRecursive = false, reRunGmMain = false){ if (isRecursive) return if (await this.waitForElement(selector, null, true)) { this.log.debug("[Navigation] " + eventMessage); @@ -458,7 +458,7 @@ class Stash extends EventTarget { } } } - gmMain(isRecursive) { + gmMain(isRecursive = false) { const location = window.location; this.log.debug(URL, window.location); @@ -467,7 +467,7 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Wall-Markers Page'); this.dispatchEvent(new Event('page:markers')); } -// create scene page + // create scene page else if (this.matchUrl(location, /\/scenes\/new/)) { this.log.debug('[Navigation] Create Scene Page'); this.dispatchEvent(new Event('page:scene:new')); @@ -766,9 +766,9 @@ class Stash extends EventTarget { this._listenForNonPageChanges(".recommendations-container-edit", "page:home:edit", "Home Page - Edit", false, true); } } - addEventListeners(events, callback) { + addEventListeners(events, callback, ...options) { events.forEach((event) => { - this.addEventListener(event, callback); + this.addEventListener(event, callback, ...options); }); } hidePluginTasks() { From 6bc891c7276bbc9cdb58ce6405294ede776b79d8 Mon Sep 17 00:00:00 2001 From: raghavan Date: Wed, 17 Jan 2024 06:10:12 +0530 Subject: [PATCH 40/91] fix silly mistake --- .../stashUserscriptLibrary.js | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 54057204..df857abc 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -45,18 +45,20 @@ class Stash extends EventTarget { this._pageUrlCheckInterval = pageUrlCheckInterval; this._detectReRenders = detectReRenders; this.fireOnHashChangesToo = true; - this.pageURLCheckTimer = setInterval(() => { - // Loop every 100 ms - if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { - this.lastPathStr = location.pathname; - this.lastQueryStr = location.search; - this.lastHashStr = location.hash; - if (this._detectReRenders) document.querySelector(".main > div").setAttribute("stashUserscriptLibrary", ""); - this.log.debug('[Navigation] Page Changed'); - this.dispatchEvent(new Event('page')); - this.gmMain(); - } - }, this._pageUrlCheckInterval); + waitForElementQuerySelector(".main > div", () => { + this.pageURLCheckTimer = setInterval(() => { + // Loop every 100 ms + if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { + this.lastPathStr = location.pathname; + this.lastQueryStr = location.search; + this.lastHashStr = location.hash; + if (this._detectReRenders) document.querySelector(".main > div").setAttribute("stashUserscriptLibrary", ""); + this.log.debug('[Navigation] Page Changed'); + this.dispatchEvent(new Event('page')); + this.gmMain(); + } + }, this._pageUrlCheckInterval); + }, 1) stashListener.addEventListener('response', (evt) => { if (evt.detail.data?.plugins) { this.getPluginVersion(evt.detail); @@ -497,8 +499,8 @@ class Stash extends EventTarget { this.dispatchEvent(new Event('page:image')); this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-details-panel']", "page:image:details", "Image Page - Details"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-file-info-panel']", "page:image:details", "Image Page - File Info"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-edit-panel']", "page:image:details", "Image Page - Edit"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-file-info-panel']", "page:image:file-info", "Image Page - File Info"); + this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-edit-panel']", "page:image:edit", "Image Page - Edit"); } // images wall else if (this.matchUrl(location, /\/images\?/)) { @@ -1130,6 +1132,18 @@ class Stash extends EventTarget { window.stash = new Stash(); +function waitForElementQuerySelector(query, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.querySelector(query); + if (element) { + callBack(query, element); + } else { + waitForElementQuerySelector(query, callBack, time); + } + }, time); +} + function waitForElementClass(elementId, callBack, time) { time = (typeof time !== 'undefined') ? time : 100; window.setTimeout(() => { @@ -1137,7 +1151,7 @@ function waitForElementClass(elementId, callBack, time) { if (element.length > 0) { callBack(elementId, element); } else { - waitForElementClass(elementId, callBack); + waitForElementClass(elementId, callBack, time); } }, time); } @@ -1149,7 +1163,7 @@ function waitForElementId(elementId, callBack, time) { if (element != null) { callBack(elementId, element); } else { - waitForElementId(elementId, callBack); + waitForElementId(elementId, callBack, time); } }, time); } @@ -1161,7 +1175,7 @@ function waitForElementByXpath(xpath, callBack, time) { if (element) { callBack(xpath, element); } else { - waitForElementByXpath(xpath, callBack); + waitForElementByXpath(xpath, callBack, time); } }, time); } From 82e450a4becb21e91c365453954660432e9e4ce6 Mon Sep 17 00:00:00 2001 From: raghavan Date: Wed, 17 Jan 2024 19:18:04 +0530 Subject: [PATCH 41/91] improve performance --- .../stashUserscriptLibrary.js | 241 ++++++++++++------ 1 file changed, 158 insertions(+), 83 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index df857abc..ff73095d 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -48,14 +48,17 @@ class Stash extends EventTarget { waitForElementQuerySelector(".main > div", () => { this.pageURLCheckTimer = setInterval(() => { // Loop every 100 ms - if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { + if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || this.lastHref !== location.href || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { + this.log.debug('[Navigation] Page Changed'); + this.dispatchEvent(new Event('page')); + this.gmMain(false, this.lastHref); this.lastPathStr = location.pathname; this.lastQueryStr = location.search; this.lastHashStr = location.hash; - if (this._detectReRenders) document.querySelector(".main > div").setAttribute("stashUserscriptLibrary", ""); - this.log.debug('[Navigation] Page Changed'); - this.dispatchEvent(new Event('page')); - this.gmMain(); + this.lastHref = location.href; + waitForElementQuerySelector(".main > div", (query, element) => { + if (this._detectReRenders) element.setAttribute("stashUserscriptLibrary", ""); + }, 10) } }, this._pageUrlCheckInterval); }, 1) @@ -384,7 +387,7 @@ class Stash extends EventTarget { get serverUrl() { return window.location.origin; } - async waitForElement(selector, timeout = null, disconnectOnPageChange = false) { + async waitForElement(selector, timeout = null, location = document.body, disconnectOnPageChange = false) { return new Promise((resolve) => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)) @@ -409,7 +412,7 @@ class Stash extends EventTarget { } }) - observer.observe(document.body, { + observer.observe(location, { childList: true, subtree: true, }) @@ -423,7 +426,7 @@ class Stash extends EventTarget { } }) } - async waitForElementDeath(selector, disconnectOnPageChange = false) { + async waitForElementDeath(selector, location = document.body, disconnectOnPageChange = false) { return new Promise((resolve) => { const observer = new MutationObserver(async () => { if (!document.querySelector(selector)) { @@ -432,7 +435,7 @@ class Stash extends EventTarget { } }) - observer.observe(document.body, { + observer.observe(location, { childList: true, subtree: true, }) @@ -446,27 +449,98 @@ class Stash extends EventTarget { } }) } - async _listenForNonPageChanges(selector, event, eventMessage, isRecursive = false, reRunGmMain = false){ + async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", eventMessage = "", isRecursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ if (isRecursive) return - if (await this.waitForElement(selector, null, true)) { + if (listenType === "tabs") { + const locationElement = await this.waitForElement(location, 10000, document.body, true) + const stash = this + let previousEvent = "" + function listenForTabClicks(domEvent) { + const clickedChild = domEvent.target ? domEvent.target : domEvent; + if(!clickedChild.classList?.contains("nav-link")) return + const tagName = clickedChild.getAttribute("data-rb-event-key") + const parentEvent = tagName.split("-")[0] + const childEvent = tagName.split("-").slice(1, -1).join("-") + event = `page:${parentEvent}:${childEvent}` + if (previousEvent === event || !condition()) return + previousEvent = event + stash.log.debug("[Navigation] " + `${parentEvent[0].toUpperCase() + parentEvent.slice(1)} Page - ${childEvent[0].toUpperCase() + childEvent.slice(1)}`); + stash.dispatchEvent(new Event(event)); + } + if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) + locationElement.addEventListener("click", listenForTabClicks); + function removeEventListenerOnPageChange() { + locationElement.removeEventListener("click", listenForTabClicks) + stash.removeEventListener("page", removeEventListenerOnPageChange) + } + stash.addEventListener("page", removeEventListenerOnPageChange) + } else if (await this.waitForElement(selector, null, location, true)) { this.log.debug("[Navigation] " + eventMessage); this.dispatchEvent(new Event(event)); - if (await this.waitForElementDeath(selector, true)) { - if (this.lastPathStr === location.pathname && !reRunGmMain) { - this._listenForNonPageChanges(selector, event, eventMessage) - } else if (this.lastPathStr === location.pathname && reRunGmMain) { - this.gmMain(true) + if (await this.waitForElementDeath(selector, location, true)) { + if (this.lastPathStr === window.location.pathname && !reRunGmMain) { + this._listenForNonPageChanges({selector: selector, event: event, eventMessage: eventMessage}) + } else if (this.lastPathStr === window.location.pathname && reRunGmMain) { + this.gmMain(true, this.lastHref) } } } + callback() } - gmMain(isRecursive = false) { + gmMain(isRecursive = false, lastHref) { const location = window.location; this.log.debug(URL, window.location); - // marker wall + // Run parent page specific functions + // detect performer edit page + if (this.matchUrl(location, /\/performers\/\d+/)) { + if(!new RegExp(/\/performers\/\d+/).test(this.lastHref)){ + this.log.debug('[Navigation] Performer Page'); + this.processTagger(); + this.dispatchEvent(new Event('page:performer')); + } + + this._listenForNonPageChanges({selector: "#performer-edit", event: "page:performer:edit", eventMessage: "Performer Page - Edit", reRunGmMain: true, callback: () => { + if (this._detectReRenders) { + this.log.debug('[Navigation] Performer Page'); + this.dispatchEvent(new Event('page:performer')); + } + }}) + } + // detect studio edit page + else if (this.matchUrl(location, /\/studios\/\d+/)) { + if(!new RegExp(/\/studios\/\d+/).test(this.lastHref)){ + this.log.debug('[Navigation] Studio Page'); + this.processTagger(); + this.dispatchEvent(new Event('page:studio')); + } + + this._listenForNonPageChanges({selector: "#studio-edit", event: "page:studio:edit", eventMessage: "Studio Page - Edit", reRunGmMain: true, callback: () => { + if (this._detectReRenders) { + this.log.debug('[Navigation] Studio Page'); + this.dispatchEvent(new Event('page:studio')); + } + }}) + } + // detect tag edit page + else if (this.matchUrl(location, /\/tags\/\d+/)) { + if(!new RegExp(/\/tags\/\d+/).test(this.lastHref)){ + this.log.debug('[Navigation] Tag Page'); + this.processTagger(); + this.dispatchEvent(new Event('page:tag')); + } + + this._listenForNonPageChanges({selector: "#tag-edit", event: "page:tag:edit", eventMessage: "Tag Page - Edit", reRunGmMain: true, callback: () => { + if (this._detectReRenders) { + this.log.debug('[Navigation] Tag Page'); + this.dispatchEvent(new Event('page:tag')); + } + }}) + } + + // markers page if (this.matchUrl(location, /\/scenes\/markers/)) { - this.log.debug('[Navigation] Wall-Markers Page'); + this.log.debug('[Navigation] Markers Page'); this.dispatchEvent(new Event('page:markers')); } // create scene page @@ -478,17 +552,16 @@ class Stash extends EventTarget { else if (this.matchUrl(location, /\/scenes\/\d+/)) { this.log.debug('[Navigation] Scene Page'); this.dispatchEvent(new Event('page:scene')); - - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-details-panel']", "page:scene:details", "Scene Page - Details", isRecursive); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-queue-panel']", "page:scene:queue", "Scene Page - Queue", isRecursive); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-markers-panel']", "page:scene:markers", "Scene Page - Markers", isRecursive); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-video-filter-panel']", "page:scene:filters", "Scene Page - Filters", isRecursive); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-file-info-panel']", "page:scene:file-info", "Scene Page - File Info", isRecursive); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='scene-edit-panel']", "page:scene:edit", "Scene Page - Edit", isRecursive); + + this._listenForNonPageChanges({ + location: ".scene-tabs .nav-tabs", + listenType: "tabs", + isRecursive: isRecursive + }) } - // scenes wall + // scenes page else if (this.matchUrl(location, /\/scenes\?/)) { - this.log.debug('[Navigation] Wall-Scenes Page'); + this.log.debug('[Navigation] Scenes Page'); this.processTagger(); this.dispatchEvent(new Event('page:scenes')); } @@ -498,13 +571,15 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Image Page'); this.dispatchEvent(new Event('page:image')); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-details-panel']", "page:image:details", "Image Page - Details"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-file-info-panel']", "page:image:file-info", "Image Page - File Info"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='image-edit-panel']", "page:image:edit", "Image Page - Edit"); + this._listenForNonPageChanges({ + location: ".image-tabs .nav-tabs", + listenType: "tabs", + isRecursive: isRecursive + }) } - // images wall + // images page else if (this.matchUrl(location, /\/images\?/)) { - this.log.debug('[Navigation] Wall-Images Page'); + this.log.debug('[Navigation] Images Page'); this.dispatchEvent(new Event('page:images')); } @@ -519,37 +594,56 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Movie Page'); this.dispatchEvent(new Event('page:movie')); } - // movies wall + // movies page else if (this.matchUrl(location, /\/movies\?/)) { - this.log.debug('[Navigation] Wall-Movies Page'); + this.log.debug('[Navigation] Movies Page'); this.dispatchEvent(new Event('page:movies')); } + // create gallery page + else if (this.matchUrl(location, /\/galleries\/new/)) { + this.log.debug('[Navigation] Create Gallery Page'); + this.dispatchEvent(new Event('page:gallery:new')); + } // gallery add page else if (this.matchUrl(location, /\/galleries\/\d+\/add/)) { - this.log.debug('[Navigation] Gallery Add Page'); - this.dispatchEvent(new Event('page:gallery:add')); - } - // create gallery page - else if (this.matchUrl(location, /\/galleries\/new/)) { - this.log.debug('[Navigation] Create Gallery Page'); - this.dispatchEvent(new Event('page:gallery:new')); + if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){ + this.log.debug('[Navigation] Gallery Page'); + this.dispatchEvent(new Event('page:gallery')); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"}) + } + + this.log.debug('[Navigation] Gallery Page - Add'); + this.dispatchEvent(new Event('page:gallery:add')); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + isRecursive: isRecursive, + listenDefaultTab: false + }) } // gallery page else if (this.matchUrl(location, /\/galleries\/\d+/)) { - this.log.debug('[Navigation] Gallery Page'); - this.dispatchEvent(new Event('page:gallery')); + if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){ + this.log.debug('[Navigation] Gallery Page'); + this.dispatchEvent(new Event('page:gallery')); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"}) + } + this.log.debug('[Navigation] Gallery Page - Images'); this.dispatchEvent(new Event('page:gallery:images')); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-details-panel']", "page:gallery:details", "Gallery Page - Details"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-file-info-panel']", "page:gallery:file-info", "Gallery Page - File Info"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-chapter-panel']", "page:gallery:chapters", "Gallery Page - Chapters"); - this._listenForNonPageChanges(".nav-link.active[data-rb-event-key='gallery-edit-panel']", "page:gallery:edit", "Gallery Page - Edit"); + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + isRecursive: isRecursive, + listenDefaultTab: false + }) } - // galleries wall + // galleries page else if (this.matchUrl(location, /\/galleries\?/)) { - this.log.debug('[Navigation] Wall-Galleries Page'); + this.log.debug('[Navigation] Galleries Page'); this.dispatchEvent(new Event('page:galleries')); } @@ -572,12 +666,7 @@ class Stash extends EventTarget { else if (this.matchUrl(location, /\/performers\/\d+\/appearswith/)) { this.log.debug('[Navigation] Performer Page - Appears With'); this.processTagger(); - this.dispatchEvent(new Event('page:performer:performers')); - } - // performer movies page - else if (this.matchUrl(location, /\/performers\/\d+\/movies/)) { - this.log.debug('[Navigation] Performer Page - Movies'); - this.dispatchEvent(new Event('page:performer:movies')); + this.dispatchEvent(new Event('page:performer:appearswith')); } // create performer page else if (this.matchUrl(location, /\/performers\/new/)) { @@ -587,14 +676,11 @@ class Stash extends EventTarget { // performer scenes page else if (this.matchUrl(location, /\/performers\/\d+\?/)) { this.log.debug('[Navigation] Performer Page - Scenes'); - this.processTagger(); this.dispatchEvent(new Event('page:performer:scenes')); - - this._listenForNonPageChanges("#performer-edit", "page:performer:edit", "Performer Page - Edit", false, true); } - // performers wall + // performers page else if (this.matchUrl(location, /\/performers\?/)) { - this.log.debug('[Navigation] Wall-Performers Page'); + this.log.debug('[Navigation] Performers Page'); this.dispatchEvent(new Event('page:performers')); } @@ -631,19 +717,11 @@ class Stash extends EventTarget { // studio scenes page else if (this.matchUrl(location, /\/studios\/\d+\?/)) { this.log.debug('[Navigation] Studio Page - Scenes'); - this.processTagger(); this.dispatchEvent(new Event('page:studio:scenes')); - - this._listenForNonPageChanges("#studio-edit", "page:studio:edit", "Studio Page - Edit", false, true); - } - // studio page - else if (this.matchUrl(location, /\/studios\/\d+/)) { - this.log.debug('[Navigation] Studio Page'); - this.dispatchEvent(new Event('page:studio')); } - // studios wall + // studios page else if (this.matchUrl(location, /\/studios\?/)) { - this.log.debug('[Navigation] Wall-Studios Page'); + this.log.debug('[Navigation] Studios Page'); this.dispatchEvent(new Event('page:studios')); } @@ -675,27 +753,24 @@ class Stash extends EventTarget { // tag scenes page else if (this.matchUrl(location, /\/tags\/\d+\?/)) { this.log.debug('[Navigation] Tag Page - Scenes'); - this.processTagger(); this.dispatchEvent(new Event('page:tag:scenes')); - - this._listenForNonPageChanges("#tag-edit", "page:tag:edit", "Tag Page - Edit", false, true); - } - // tag page - else if (this.matchUrl(location, /\/tags\/\d+/)) { - this.log.debug('[Navigation] Tag Page'); - this.dispatchEvent(new Event('page:tag')); } - // tags wall + // tags page else if (this.matchUrl(location, /\/tags\?/)) { - this.log.debug('[Navigation] Wall-Tags Page'); + this.log.debug('[Navigation] Tags Page'); this.dispatchEvent(new Event('page:tags')); } // settings page tasks tab else if (this.matchUrl(location, /\/settings\?tab=tasks/)) { + if(!new RegExp(/\/settings\?/).test(this.lastHref)){ + this.log.debug('[Navigation] Settings Page'); + this.dispatchEvent(new Event('page:settings')); + this.hidePluginTasks(); + } + this.log.debug('[Navigation] Settings Page Tasks Tab'); this.dispatchEvent(new Event('page:settings:tasks')); - this.hidePluginTasks(); } // settings page library tab else if (this.matchUrl(location, /\/settings\?tab=library/)) { @@ -765,7 +840,7 @@ class Stash extends EventTarget { this.log.debug('[Navigation] Home Page'); this.dispatchEvent(new Event('page:home')); - this._listenForNonPageChanges(".recommendations-container-edit", "page:home:edit", "Home Page - Edit", false, true); + this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "page:home:edit", eventMessage: "Home Page - Edit", reRunGmMain: true}) } } addEventListeners(events, callback, ...options) { From 923d3075df0fae0a785e5de34ff52526ee6bfe7b Mon Sep 17 00:00:00 2001 From: raghavan Date: Wed, 17 Jan 2024 19:35:10 +0530 Subject: [PATCH 42/91] fix mutation observer disconnect instance --- plugins/stashUserscriptLibrary/stashUserscriptLibrary.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index ff73095d..ecaeeb5d 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -417,10 +417,11 @@ class Stash extends EventTarget { subtree: true, }) +const stash = this if (disconnectOnPageChange) { function disconnect() { observer.disconnect() - this.removeEventListener("page", disconnect) + stash.removeEventListener("page", disconnect) } this.addEventListener("page", disconnect) } @@ -440,10 +441,11 @@ class Stash extends EventTarget { subtree: true, }) +const stash = this if (disconnectOnPageChange) { function disconnect() { observer.disconnect() - this.removeEventListener("page", disconnect) + stash.removeEventListener("page", disconnect) } this.addEventListener("page", disconnect) } From 03fc23b2fe8ec80ca182aff69e897b4213fad011 Mon Sep 17 00:00:00 2001 From: raghavan Date: Wed, 17 Jan 2024 19:39:14 +0530 Subject: [PATCH 43/91] fix formatting --- plugins/stashUserscriptLibrary/stashUserscriptLibrary.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index ecaeeb5d..49f91eef 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -417,7 +417,7 @@ class Stash extends EventTarget { subtree: true, }) -const stash = this + const stash = this if (disconnectOnPageChange) { function disconnect() { observer.disconnect() @@ -441,7 +441,7 @@ const stash = this subtree: true, }) -const stash = this + const stash = this if (disconnectOnPageChange) { function disconnect() { observer.disconnect() From 63f9e2195d65b4668e7f1fc0ac15fdb8bc54899c Mon Sep 17 00:00:00 2001 From: Written2001 <121555133+Written2001@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:42:38 +0100 Subject: [PATCH 44/91] [Theme] - Adding RoundedYellow Theme (#211) --- themes/Theme-RoundedYellow/README.md | 5 + .../Theme-RoundedYellow.css | 169 ++++++++++++++++++ .../Theme-RoundedYellow.yml | 6 + 3 files changed, 180 insertions(+) create mode 100644 themes/Theme-RoundedYellow/README.md create mode 100644 themes/Theme-RoundedYellow/Theme-RoundedYellow.css create mode 100644 themes/Theme-RoundedYellow/Theme-RoundedYellow.yml diff --git a/themes/Theme-RoundedYellow/README.md b/themes/Theme-RoundedYellow/README.md new file mode 100644 index 00000000..b32283fa --- /dev/null +++ b/themes/Theme-RoundedYellow/README.md @@ -0,0 +1,5 @@ +A theme with yellow accent colour and rounded corners. + +# Preview +![2024-01-03_14-37-11_blur](https://github.com/Written2001/CommunityScripts/assets/121555133/96932001-4b46-4e97-b4de-5e7cb61608fb) +![2024-01-03_14-37-01_blur](https://github.com/Written2001/CommunityScripts/assets/121555133/e999ae6b-5e53-418c-a561-00c658afba3e) diff --git a/themes/Theme-RoundedYellow/Theme-RoundedYellow.css b/themes/Theme-RoundedYellow/Theme-RoundedYellow.css new file mode 100644 index 00000000..61510771 --- /dev/null +++ b/themes/Theme-RoundedYellow/Theme-RoundedYellow.css @@ -0,0 +1,169 @@ +:root{ + --bgcol: #101118; + --searchbgcol: #1b1c28; + --btnbordercol: #202b33; + --secbtncol: #171822; + --btnaccentcol: #cfad0b; + --cardbgcol: #1f282c; + --navbarcol: #212529; + } + + body { + background-color: var(--bgcol); + } + + .bg-secondary { + background-color: var(--searchbgcol)!important; + } + + .pagination .btn:first-child {border-left: 1px solid var(--btnbordercol);} + .pagination .btn:last-child {border-right: 1px solid var(--btnbordercol);} + + .btn-secondary { + border-color: var(--btnbordercol); + background-color: var(--bgcol); + } + + .btn-secondary:disabled { + border-color: var(--btnbordercol); + background-color: var(--secbtncol); + } + + .btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show>.btn-secondary.dropdown-toggle { + background-color: var(--secbtncol); + border: 1px solid var(--btnbordercol); + color: var(--btnaccentcol); + border-bottom: 3px solid var(--btnaccentcol); + } + + .card { + background-color: var(--cardbgcol); + border-radius: 30px; + } + + .progress-indicator { + background-color: var(--btnaccentcol); + } + + .bg-dark{ + background-color: var(--navbarcol)!important; + } + + .btn.active:not(.disabled), .btn.active.minimal:not(.disabled) { + background-color: transparent; + color: var(--btnaccentcol); + border-bottom: 3px solid var(--btnaccentcol); + } + + .btn-primary{ + background-color: var(--btnaccentcol); + border-color: var(--btnaccentcol); + } + + .btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show>.btn-primary.dropdown-toggle { + background-color: transparent; + border-color: var(--btnaccentcol); + } + + .btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show>.btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0; + } + + .btn-primary:focus, .btn-primary.focus { + background-color: var(--btnaccentcol); + border-color: var(--btnaccentcol); + box-shadow: 0 0 0 0; + } + + .btn-primary:hover { + color: #fff; + background-color: var(--btnaccentcol); + border-color: var(--btnaccentcol); + } + + div.react-select__menu, div.dropdown-menu { + background-color: var(--bgcol); + color: #f5f8fa; + z-index: 1600; + } + + .modal-header, .modal-body, .modal-footer { + background-color: var(--bgcol); + color: #f5f8fa; + } + + .edit-filter-dialog .criterion-list .card .filter-item-header:focus { + border-color: var(--btnaccentcol); + border-radius: calc(.375rem - 1px); + box-shadow: inset 0 0 0 0.1rem var(--btnaccentcol); + outline: 0; + } + + .nav-tabs .nav-link.active { + border-bottom: 2px solid; + color: var(--btnaccentcol); + } + + .nav-tabs .nav-link.active:hover { + border-bottom-color: var(--btnaccentcol); + cursor: default; + } + + .job-table.card { + background-color: var(--bgcol); + height: 10em; + margin-bottom: 30px; + overflow-y: auto; + padding: .5rem 15px; + } + + .progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: var(--btnaccentcol); + transition: width .6s ease; + } + + .grid-card .progress-bar { + background-color: rgba(115,133,159,.5); + bottom: 5px; + display: block; + height: 5px; + position: absolute; + width: 100%; + } + + .grid-card .progress-indicator { + background-color: var(--btnaccentcol); + height: 5px; + } + + .hover-scrubber .hover-scrubber-indicator .hover-scrubber-indicator-marker { + background-color: var(--btnaccentcol); + bottom: 0; + height: 5px; + position: absolute; + } + + #tasks-panel .tasks-panel-queue { + background-color: var(--bgcol); + margin-top: -1rem; + padding-bottom: .25rem; + padding-top: 1rem; + position: sticky; + top: 3rem; + z-index: 2; + } + + #scene-edit-details .edit-buttons-container { + background-color: var(--bgcol); + position: sticky; + top: 0; + z-index: 3; + } + \ No newline at end of file diff --git a/themes/Theme-RoundedYellow/Theme-RoundedYellow.yml b/themes/Theme-RoundedYellow/Theme-RoundedYellow.yml new file mode 100644 index 00000000..a568e549 --- /dev/null +++ b/themes/Theme-RoundedYellow/Theme-RoundedYellow.yml @@ -0,0 +1,6 @@ +name: Theme - Rounded Yellow +description: Theme with rounded corners and yellow accents +version: 1.0 +ui: + css: + - Theme-RoundedYellow.css From 6af9111dfc291ed24dcd84bd74d7ced0d9416744 Mon Sep 17 00:00:00 2001 From: raghavan Date: Thu, 18 Jan 2024 12:07:56 +0530 Subject: [PATCH 45/91] miner fix --- .../stashUserscriptLibrary.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 49f91eef..6e2ee885 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -45,7 +45,7 @@ class Stash extends EventTarget { this._pageUrlCheckInterval = pageUrlCheckInterval; this._detectReRenders = detectReRenders; this.fireOnHashChangesToo = true; - waitForElementQuerySelector(".main > div", () => { + waitForElementQuerySelector(this._detectReRenders ? ".main > div" : "html", () => { this.pageURLCheckTimer = setInterval(() => { // Loop every 100 ms if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || this.lastHref !== location.href || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { @@ -56,8 +56,8 @@ class Stash extends EventTarget { this.lastQueryStr = location.search; this.lastHashStr = location.hash; this.lastHref = location.href; - waitForElementQuerySelector(".main > div", (query, element) => { - if (this._detectReRenders) element.setAttribute("stashUserscriptLibrary", ""); + if (this._detectReRenders) waitForElementQuerySelector(".main > div", (query, element) => { + element.setAttribute("stashUserscriptLibrary", ""); }, 10) } }, this._pageUrlCheckInterval); @@ -496,7 +496,7 @@ class Stash extends EventTarget { // Run parent page specific functions // detect performer edit page if (this.matchUrl(location, /\/performers\/\d+/)) { - if(!new RegExp(/\/performers\/\d+/).test(this.lastHref)){ + if(!new RegExp(/\/performers\/\d+/).test(lastHref)){ this.log.debug('[Navigation] Performer Page'); this.processTagger(); this.dispatchEvent(new Event('page:performer')); @@ -511,7 +511,7 @@ class Stash extends EventTarget { } // detect studio edit page else if (this.matchUrl(location, /\/studios\/\d+/)) { - if(!new RegExp(/\/studios\/\d+/).test(this.lastHref)){ + if(!new RegExp(/\/studios\/\d+/).test(lastHref)){ this.log.debug('[Navigation] Studio Page'); this.processTagger(); this.dispatchEvent(new Event('page:studio')); @@ -526,7 +526,7 @@ class Stash extends EventTarget { } // detect tag edit page else if (this.matchUrl(location, /\/tags\/\d+/)) { - if(!new RegExp(/\/tags\/\d+/).test(this.lastHref)){ + if(!new RegExp(/\/tags\/\d+/).test(lastHref)){ this.log.debug('[Navigation] Tag Page'); this.processTagger(); this.dispatchEvent(new Event('page:tag')); @@ -765,7 +765,7 @@ class Stash extends EventTarget { // settings page tasks tab else if (this.matchUrl(location, /\/settings\?tab=tasks/)) { - if(!new RegExp(/\/settings\?/).test(this.lastHref)){ + if(!new RegExp(/\/settings\?/).test(lastHref)){ this.log.debug('[Navigation] Settings Page'); this.dispatchEvent(new Event('page:settings')); this.hidePluginTasks(); From 666c038c57bfd20fc6d5b554c03cc6f538494dbd Mon Sep 17 00:00:00 2001 From: Tweeticoats Date: Fri, 19 Jan 2024 21:14:29 +1030 Subject: [PATCH 46/91] Bugfixes, fixing extra urls and gallery info fetching. --- plugins/timestampTrade/timestampTrade.py | 53 ++++++++++++++--------- plugins/timestampTrade/timestampTrade.yml | 4 ++ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/plugins/timestampTrade/timestampTrade.py b/plugins/timestampTrade/timestampTrade.py index 352eec1e..13d84b62 100644 --- a/plugins/timestampTrade/timestampTrade.py +++ b/plugins/timestampTrade/timestampTrade.py @@ -24,7 +24,7 @@ def processScene(s): log.debug('scene has skip sync tag') return log.debug('looking up markers for stash id: '+sid['stash_id']) - res = requests.post('https://timestamp.trade/get-markers/' + sid['stash_id'], json=s) + res = request_s.post('https://timestamp.trade/get-markers/' + sid['stash_id'], json=s) md = res.json() if md.get('marker'): log.info('api returned markers for scene: ' + s['title'] + ' marker count: ' + str(len(md['marker']))) @@ -60,12 +60,13 @@ def processScene(s): 'urls': gal['urls'], 'date': gal['date'], 'rating100': gal['rating100'], - 'studio_id': gal['studio']['id'], 'performer_ids': [x['id'] for x in gal['performers']], 'tag_ids': [x['id'] for x in gal['tags']], 'scene_ids': [x['id'] for x in gal['scenes']], 'details': gal['details'] } + if 'studio' in gal: + gallery['studio_id']= gal['studio']['id'] if len(gal['urls']) == 0: log.debug('no urls on gallery, needs new metadata') gallery['urls'].extend([x['url'] for x in g['urls']]) @@ -126,13 +127,14 @@ def processScene(s): needs_update=True if settings['extraUrls']: - extra_urls=s['urls'] - for url in md['urls']: - if url['url'] not in s['urls']: - extra_urls.append(url['url']) - needs_update=True - if needs_update: - new_scene['urls']=extra_urls + if 'urls' in md and md['urls']: + extra_urls=s['urls'] + for url in md['urls']: + if url['url'] not in s['urls']: + extra_urls.append(url['url']) + needs_update=True + if needs_update: + new_scene['urls']=extra_urls if needs_update: log.debug('new scene update: %s' % (new_scene,)) stash.update_scene(new_scene) @@ -159,7 +161,7 @@ def processAll(): time.sleep(2) -def submitScene(): +def submitScene(query): scene_fgmt = """title details url @@ -226,6 +228,7 @@ def submitScene(): } } movies{ + scene_index movie{ name url @@ -244,13 +247,11 @@ def submitScene(): """ - - skip_submit_tag_id = stash.find_tag('[Timestamp: Skip Submit]', create=True).get("id") - count = stash.find_scenes(f={"has_markers": "true","tags":{"depth":0,"excludes":[skip_submit_tag_id],"modifier":"INCLUDES_ALL","value":[]}}, filter={"per_page": 1}, get_count=True)[0] + count = stash.find_scenes(f=query, filter={"per_page": 1}, get_count=True)[0] i=0 for r in range(1, math.ceil(count/per_page) + 1): log.info('submitting scenes: %s - %s %0.1f%%' % ((r - 1) * per_page,r * per_page,(i/count)*100,)) - scenes = stash.find_scenes(f={"has_markers": "true","tags":{"depth":0,"excludes":[skip_submit_tag_id],"modifier":"INCLUDES_ALL","value":[]}}, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) + scenes = stash.find_scenes(f=query, filter={"page": r, "per_page": per_page},fragment=scene_fgmt) for s in scenes: log.debug("submitting scene: " + str(s)) request_s.post('https://timestamp.trade/submit-stash', json=s) @@ -335,7 +336,7 @@ def submitGallery(): def processGalleries(): skip_sync_tag_id = stash.find_tag('[Timestamp: Skip Sync]', create=True).get("id") - count=get_count = stash.find_galleries(f={"url": {"value": "", "modifier": "IS_NULL"}, + count= stash.find_galleries(f={"url": {"value": "", "modifier": "IS_NULL"}, "tags": {"depth": 0, "excludes": [skip_sync_tag_id], "modifier": "INCLUDES_ALL", "value": []}},filter={"per_page": 1},get_count=True)[0] tag_cache={} @@ -454,20 +455,32 @@ def getImages(gallery_id): stash = StashInterface(FRAGMENT_SERVER) config=stash.get_configuration()['plugins'] settings={ - 'createGalleryFromScene':True, - 'createMovieFromScene':True, - 'extraUrls':True, + 'createGalleryFromScene':False, + 'createMovieFromScene':False, + 'extraUrls': False, } if 'timestampTrade' in config: settings.update(config['timestampTrade']) -log.info('config: %s ' % (settings,)) +log.debug('config: %s ' % (settings,)) if 'mode' in json_input['args']: PLUGIN_ARGS = json_input['args']["mode"] if 'submitScene' in PLUGIN_ARGS: - submitScene() + skip_submit_tag_id = stash.find_tag('[Timestamp: Skip Submit]', create=True).get("id") + query={ + "has_markers": "true", + "tags": {"depth": 0, "excludes": [skip_submit_tag_id], "modifier": "INCLUDES_ALL","value": []} + } + submitScene(query) + elif 'submitMovieScene' in PLUGIN_ARGS: + skip_submit_tag_id = stash.find_tag('[Timestamp: Skip Submit]', create=True).get("id") + query={ + "movies": {"modifier": "NOT_NULL", "value": [] }, + "tags": {"depth": 0, "excludes": [skip_submit_tag_id], "modifier": "INCLUDES_ALL", "value": []} + } + submitScene(query) elif 'submitGallery' in PLUGIN_ARGS: submitGallery() elif 'processGallery' in PLUGIN_ARGS: diff --git a/plugins/timestampTrade/timestampTrade.yml b/plugins/timestampTrade/timestampTrade.yml index aa1f08da..bfa9ac79 100644 --- a/plugins/timestampTrade/timestampTrade.yml +++ b/plugins/timestampTrade/timestampTrade.yml @@ -35,6 +35,10 @@ tasks: description: Submit markers to timestamp.trade defaultArgs: mode: submitScene + - name: 'Submit Scenes with linked movies' + description: Submit movie information to timestamp.trade + defaultArgs: + mode: submitMovieScene - name: 'Sync' description: Get markers for all scenes with a stashid defaultArgs: From 36b5ad44757cdec35816ed40e71a54267c63c945 Mon Sep 17 00:00:00 2001 From: Tweeticoats <60335703+Tweeticoats@users.noreply.github.com> Date: Fri, 19 Jan 2024 21:35:06 +1030 Subject: [PATCH 47/91] Update miscTags.yml Fix description --- plugins/miscTags/miscTags.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/miscTags/miscTags.yml b/plugins/miscTags/miscTags.yml index 6e7da599..3bc8f0ad 100644 --- a/plugins/miscTags/miscTags.yml +++ b/plugins/miscTags/miscTags.yml @@ -26,6 +26,6 @@ hooks: - Scene.Update.Post tasks: - name: 'process all' - description: Submit markers to timestamp.trade + description: Process all scenes and add extra tags if configured defaultArgs: mode: processScenes From 810b9db274261f52cb0416ae909b5d409e63b41d Mon Sep 17 00:00:00 2001 From: Tweeticoats <60335703+Tweeticoats@users.noreply.github.com> Date: Fri, 19 Jan 2024 21:42:31 +1030 Subject: [PATCH 48/91] Update miscTags.py Fix bug if vr tag has not been configured --- plugins/miscTags/miscTags.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/miscTags/miscTags.py b/plugins/miscTags/miscTags.py index 3f47cf50..4bea8eff 100644 --- a/plugins/miscTags/miscTags.py +++ b/plugins/miscTags/miscTags.py @@ -93,11 +93,15 @@ def processVRTags(scene,tags): found=True if found: if 'vrTag' in stash.get_configuration()['ui']: - vr_tag=stash.get_configuration()['ui']['vrTag'] - if vr_tag: - tags.append(vr_tag) - else: - tags.append('VR') + if 'vrTag' in stash.get_configuration()['ui']: + vr_tag=stash.get_configuration()['ui']['vrTag'] + if vr_tag: + tags.append(vr_tag) + else: + tags.append('VR') + else: + tags.append('VR') + else: tags.append('VR') return None @@ -142,8 +146,6 @@ def processScenes(): PLUGIN_ARGS = json_input['args']["mode"] if 'processScenes' in PLUGIN_ARGS: processScenes() - elif 'test' in PLUGIN_ARGS: - log.debug(stash.get_configuration()['ui']['vrTag']) @@ -151,4 +153,4 @@ def processScenes(): id=json_input['args']['hookContext']['id'] if json_input['args']['hookContext']['type']=='Scene.Update.Post': scene=stash.find_scene(id) - processScene(scene) \ No newline at end of file + processScene(scene) From 2e11d3557e0190345a00d57549b7a0a474b55f66 Mon Sep 17 00:00:00 2001 From: Maista6969 Date: Fri, 19 Jan 2024 12:42:21 +0100 Subject: [PATCH 49/91] Remove dangling else clause in miscTags --- plugins/miscTags/miscTags.py | 14 +++++--------- plugins/miscTags/miscTags.yml | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/plugins/miscTags/miscTags.py b/plugins/miscTags/miscTags.py index 4bea8eff..1aa08201 100644 --- a/plugins/miscTags/miscTags.py +++ b/plugins/miscTags/miscTags.py @@ -93,15 +93,11 @@ def processVRTags(scene,tags): found=True if found: if 'vrTag' in stash.get_configuration()['ui']: - if 'vrTag' in stash.get_configuration()['ui']: - vr_tag=stash.get_configuration()['ui']['vrTag'] - if vr_tag: - tags.append(vr_tag) - else: - tags.append('VR') - else: - tags.append('VR') - + vr_tag=stash.get_configuration()['ui']['vrTag'] + if vr_tag: + tags.append(vr_tag) + else: + tags.append('VR') else: tags.append('VR') return None diff --git a/plugins/miscTags/miscTags.yml b/plugins/miscTags/miscTags.yml index 3bc8f0ad..7d88e899 100644 --- a/plugins/miscTags/miscTags.yml +++ b/plugins/miscTags/miscTags.yml @@ -20,12 +20,12 @@ settings: description: Comma seperated list of studio number in your instance to tag with export_devor,FLAT for use with stash-vr-companion type: STRING hooks: - - name: process Scene - description: Makes Markers checking against timestamp.trade + - name: Process Scene + description: Adds extra tags to scenes triggeredBy: - Scene.Update.Post tasks: - - name: 'process all' - description: Process all scenes and add extra tags if configured + - name: Process all + description: Process all scenes and add extra tags if configured defaultArgs: mode: processScenes From cc7882da281e12308db6b5d0fa29a261e6dce54a Mon Sep 17 00:00:00 2001 From: raghavan Date: Fri, 19 Jan 2024 20:33:34 +0530 Subject: [PATCH 50/91] improved page listening --- .../sceneCoverCropper/sceneCoverCropper.js | 2 +- plugins/stashUserscriptLibrary/custom.js | 1434 +++++++++++++++++ .../stashUserscriptLibrary.js | 802 ++++----- plugins/stats/stats.js | 2 +- 4 files changed, 1869 insertions(+), 371 deletions(-) create mode 100644 plugins/stashUserscriptLibrary/custom.js diff --git a/plugins/sceneCoverCropper/sceneCoverCropper.js b/plugins/sceneCoverCropper/sceneCoverCropper.js index 7d53c4ee..38086816 100644 --- a/plugins/sceneCoverCropper/sceneCoverCropper.js +++ b/plugins/sceneCoverCropper/sceneCoverCropper.js @@ -139,7 +139,7 @@ cropBtnContainer.appendChild(cropInfo); } - stash.addEventListener('page:scene', function () { + stash.addEventListener('stash:page:scene', function () { waitForElementId('scene-edit-details', setupCropper); }); })(); diff --git a/plugins/stashUserscriptLibrary/custom.js b/plugins/stashUserscriptLibrary/custom.js new file mode 100644 index 00000000..f13164c5 --- /dev/null +++ b/plugins/stashUserscriptLibrary/custom.js @@ -0,0 +1,1434 @@ +const stashListener = new EventTarget(); + +const { + fetch: originalFetch +} = window; + +window.fetch = async (...args) => { + let [resource, config] = args; + // request interceptor here + const response = await originalFetch(resource, config); + // response interceptor here + const contentType = response.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1 && resource.endsWith('/graphql')) { + try { + const data = await response.clone().json(); + stashListener.dispatchEvent(new CustomEvent('response', { + 'detail': data + })); + } catch (e) { + + } + } + return response; +}; + +class Logger { + constructor(enabled) { + this.enabled = enabled; + } + debug() { + if (this.enabled) return; + console.debug(...arguments); + } +} + + +class Stash extends EventTarget { + constructor({ + pageUrlCheckInterval = 100, + detectReRenders = false, // detects if .main element is re-rendered. eg: When you are in scenes page and clicking the scenes nav tab the url wont change but the elements are re-rendered, So with this you can listen and alter the elements inside the .main node + logging = false + } = {}) { + super(); + this.log = new Logger(logging); + this._pageUrlCheckInterval = pageUrlCheckInterval; + this._detectReRenders = detectReRenders; + this.fireOnHashChangesToo = true; + this._lastPathStr = ""; + this._lastQueryStr = ""; + this._lastHashStr = ""; + this._lastHref = ""; + this._lastStashPageEvent = ""; + this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => { + this._pageURLCheckTimerId = setInterval(() => { + // Loop every 100 ms + if ( + this._lastPathStr !== location.pathname || + this._lastQueryStr !== location.search || + (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) || + this._lastHref !== location.href || + (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders) + ) { + this._dispatchPageEvent("stash:page", false) + this.gmMain({ + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); + this._lastPathStr = location.pathname + this._lastQueryStr = location.search + this._lastHashStr = location.hash + this._lastHref = location.href + if (this._detectReRenders) { + this.waitForElement(".main > div", 10000).then((element) => { + element.setAttribute("stashUserscriptLibrary", ""); + }) + } + } + }, this._pageUrlCheckInterval); + }) + stashListener.addEventListener('response', (evt) => { + if (evt.detail.data?.plugins) { + this.getPluginVersion(evt.detail); + } + this.processRemoteScenes(evt.detail); + this.processScene(evt.detail); + this.processScenes(evt.detail); + this.processStudios(evt.detail); + this.processPerformers(evt.detail); + this.processApiKey(evt.detail); + this.dispatchEvent(new CustomEvent('stash:response', { + 'detail': evt.detail + })); + }); + stashListener.addEventListener('pluginVersion', (evt) => { + if (this.pluginVersion !== evt.detail) { + this.pluginVersion = evt.detail; + this.dispatchEvent(new CustomEvent('stash:pluginVersion', { + 'detail': evt.detail + })); + } + }); + this.version = [0, 0, 0]; + this.getVersion(); + this.pluginVersion = null; + this.getPlugins().then(plugins => this.getPluginVersion(plugins)); + this.visiblePluginTasks = ['Userscript Functions']; + this.settingsCallbacks = []; + this.settingsId = 'userscript-settings'; + this.remoteScenes = {}; + this.scenes = {}; + this.studios = {}; + this.performers = {}; + this.userscripts = []; + this._pageListeners = {}; + this.assignPageListeners() + } + async getVersion() { + const reqData = { + "operationName": "", + "variables": {}, + "query": `query version { + version { + version + } + }` + }; + const data = await this.callGQL(reqData); + const versionString = data.data.version.version; + this.version = versionString.substring(1).split('.').map(o => parseInt(o)); + } + compareVersion(minVersion) { + let [currMajor, currMinor, currPatch = 0] = this.version; + let [minMajor, minMinor, minPatch = 0] = minVersion.split('.').map(i => parseInt(i)); + if (currMajor > minMajor) return 1; + if (currMajor < minMajor) return -1; + if (currMinor > minMinor) return 1; + if (currMinor < minMinor) return -1; + return 0; + + } + comparePluginVersion(minPluginVersion) { + if (!this.pluginVersion) return -1; + let [currMajor, currMinor, currPatch = 0] = this.pluginVersion.split('.').map(i => parseInt(i)); + let [minMajor, minMinor, minPatch = 0] = minPluginVersion.split('.').map(i => parseInt(i)); + if (currMajor > minMajor) return 1; + if (currMajor < minMajor) return -1; + if (currMinor > minMinor) return 1; + if (currMinor < minMinor) return -1; + return 0; + + } + async runPluginTask(pluginId, taskName, args = []) { + const reqData = { + "operationName": "RunPluginTask", + "variables": { + "plugin_id": pluginId, + "task_name": taskName, + "args": args + }, + "query": "mutation RunPluginTask($plugin_id: ID!, $task_name: String!, $args: [PluginArgInput!]) {\n runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)\n}\n" + }; + return this.callGQL(reqData); + } + async callGQL(reqData) { + const options = { + method: 'POST', + body: JSON.stringify(reqData), + headers: { + 'Content-Type': 'application/json' + } + } + + try { + const res = await window.fetch('/graphql', options); + // this.log.debug(res); + return res.json(); + } catch (err) { + console.error(err); + } + } + async getFreeOnesStats(link) { + try { + const doc = await fetch(link) + .then(function(response) { + // When the page is loaded convert it to text + return response.text() + }) + .then(function(html) { + // Initialize the DOM parser + var parser = new DOMParser(); + + // Parse the text + var doc = parser.parseFromString(html, "text/html"); + + // You can now even select part of that html as you would in the regular DOM + // Example: + // var docArticle = doc.querySelector('article').innerHTML; + + console.log(doc); + return doc + }) + .catch(function(err) { + console.log('Failed to fetch page: ', err); + }); + + var data = new Object(); + data.rank = doc.querySelector('rank-chart-button'); + console.log(data.rank); + data.views = doc.querySelector('.d-none.d-m-flex.flex-column.align-items-center.global-header > div.font-weight-bold').textContent; + data.votes = '0' + return JSON.stringify(data); + } catch (err) { + console.error(err); + } + } + async getPlugins() { + const reqData = { + "operationName": "Plugins", + "variables": {}, + "query": `query Plugins { + plugins { + id + name + description + url + version + tasks { + name + description + __typename + } + hooks { + name + description + hooks + } + } + } + ` + }; + return this.callGQL(reqData); + } + async getPluginVersion(plugins) { + let version = null; + for (const plugin of plugins?.data?.plugins || []) { + if (plugin.id === 'userscript_functions') { + version = plugin.version; + } + } + stashListener.dispatchEvent(new CustomEvent('pluginVersion', { + 'detail': version + })); + } + async getStashBoxes() { + const reqData = { + "operationName": "Configuration", + "variables": {}, + "query": `query Configuration { + configuration { + general { + stashBoxes { + endpoint + api_key + name + } + } + } + }` + }; + return this.callGQL(reqData); + } + async getApiKey() { + const reqData = { + "operationName": "Configuration", + "variables": {}, + "query": `query Configuration { + configuration { + general { + apiKey + } + } + }` + }; + return this.callGQL(reqData); + } + matchUrl(href, fragment) { + const regexp = concatRegexp(new RegExp(window.location.origin), fragment); + // this.log.debug(regexp, location.href.match(regexp)); + return href.match(regexp) != null; + } + createSettings() { + waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => { + let section; + if (!document.getElementById(this.settingsId)) { + section = document.createElement("div"); + section.setAttribute('id', this.settingsId); + section.classList.add('setting-section'); + section.innerHTML = `

Userscript Settings

`; + el.appendChild(section); + + const expectedApiKey = (await this.getApiKey())?.data?.configuration?.general?.apiKey || ''; + const expectedUrl = window.location.origin; + + const serverUrlInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-url', 'userscript-server-url', 'Stash Server URL', '', 'Server URL…', true); + serverUrlInput.addEventListener('change', () => { + const value = serverUrlInput.value || ''; + if (value) { + this.updateConfigValueTask('STASH', 'url', value); + alert(`Userscripts plugin server URL set to ${value}`); + } else { + this.getConfigValueTask('STASH', 'url').then(value => { + serverUrlInput.value = value; + }); + } + }); + serverUrlInput.disabled = true; + serverUrlInput.value = expectedUrl; + this.getConfigValueTask('STASH', 'url').then(value => { + if (value !== expectedUrl) { + return this.updateConfigValueTask('STASH', 'url', expectedUrl); + } + }); + + const apiKeyInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-apikey', 'userscript-server-apikey', 'Stash API Key', '', 'API Key…', true); + apiKeyInput.addEventListener('change', () => { + const value = apiKeyInput.value || ''; + this.updateConfigValueTask('STASH', 'api_key', value); + if (value) { + alert(`Userscripts plugin server api key set to ${value}`); + } else { + alert(`Userscripts plugin server api key value cleared`); + } + }); + apiKeyInput.disabled = true; + apiKeyInput.value = expectedApiKey; + this.getConfigValueTask('STASH', 'api_key').then(value => { + if (value !== expectedApiKey) { + return this.updateConfigValueTask('STASH', 'api_key', expectedApiKey); + } + }); + } else { + section = document.getElementById(this.settingsId); + } + + for (const callback of this.settingsCallbacks) { + callback(this.settingsId, section); + } + + if (this.pluginVersion) { + this.dispatchEvent(new CustomEvent('stash:pluginVersion', { + 'detail': this.pluginVersion + })); + } + + }); + } + addSystemSetting(callback) { + const section = document.getElementById(this.settingsId); + if (section) { + callback(this.settingsId, section); + } + this.settingsCallbacks.push(callback); + } + async createSystemSettingCheckbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader) { + const section = document.createElement("div"); + section.setAttribute('id', settingsId); + section.classList.add('card'); + section.style.display = 'none'; + section.innerHTML = `
+
+

${settingsHeader}

+
${settingsSubheader}
+
+
+
+ + +
+
+
`; + containerEl.appendChild(section); + return document.getElementById(inputId); + } + async createSystemSettingTextbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader, placeholder, visible) { + const section = document.createElement("div"); + section.setAttribute('id', settingsId); + section.classList.add('card'); + section.style.display = visible ? 'flex' : 'none'; + section.innerHTML = `
+
+

${settingsHeader}

+
${settingsSubheader}
+
+
+
+ +
+
+
`; + containerEl.appendChild(section); + return document.getElementById(inputId); + } + get serverUrl() { + return window.location.origin; + } + async waitForElement(selector, timeout = null, location = document.body, disconnectOnPageChange = false) { + return new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)) + } + + const observer = new MutationObserver(async () => { + if (document.querySelector(selector)) { + resolve(document.querySelector(selector)) + observer.disconnect() + } else { + if (timeout) { + async function timeOver() { + return new Promise((resolve) => { + setTimeout(() => { + observer.disconnect() + resolve(false) + }, timeout) + }) + } + resolve(await timeOver()) + } + } + }) + + observer.observe(location, { + childList: true, + subtree: true, + }) + + const stash = this + if (disconnectOnPageChange) { + function disconnect() { + observer.disconnect() + stash.removeEventListener("stash:page", disconnect) + } + stash.addEventListener("stash:page", disconnect) + } + }) + } + async waitForElementDeath(selector, location = document.body, disconnectOnPageChange = false) { + return new Promise((resolve) => { + const observer = new MutationObserver(async () => { + if (!document.querySelector(selector)) { + resolve(true) + observer.disconnect() + } + }) + + observer.observe(location, { + childList: true, + subtree: true, + }) + + const stash = this + if (disconnectOnPageChange) { + function disconnect() { + observer.disconnect() + stash.removeEventListener("stash:page", disconnect) + } + stash.addEventListener("stash:page", disconnect) + } + }) + } + async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ + if (recursive) return + if (listenType === "tabs") { + const locationElement = await this.waitForElement(location, 10000, document.body, true) + const stash = this + let previousEvent = "" + function listenForTabClicks(domEvent) { + const clickedChild = domEvent.target ? domEvent.target : domEvent; + if(!clickedChild.classList?.contains("nav-link")) return + const tagName = clickedChild.getAttribute("data-rb-event-key") + const parentEvent = tagName.split("-")[0] + const childEvent = tagName.split("-").slice(1, -1).join("-") + event = `stash:page:${parentEvent}:${childEvent}` + if (previousEvent === event || !condition()) return + previousEvent = event + stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false) + stash._dispatchPageEvent(event) + } + if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) + locationElement.addEventListener("click", listenForTabClicks); + function removeEventListenerOnPageChange() { + locationElement.removeEventListener("click", listenForTabClicks) + stash.removeEventListener("stash:page", removeEventListenerOnPageChange) + } + stash.addEventListener("stash:page", removeEventListenerOnPageChange) + } else if (await this.waitForElement(selector, null, location, true)) { + this._dispatchPageEvent(event) + if (await this.waitForElementDeath(selector, location, true)) { + if (this._lastPathStr === window.location.pathname && !reRunGmMain) { + await this._listenForNonPageChanges({selector: selector, event: event}) + } else if (this._lastPathStr === window.location.pathname && reRunGmMain) { + this.gmMain({ + recursive: true, + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); + } + } + } + callback() + } + _dispatchPageEvent(event, addToHistory = true) { + this.dispatchEvent(new CustomEvent(event, { + detail: { + event: event, + lastEventState: { + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + } + } + })) + if (addToHistory) { + this.log.debug(`[Navigation] ${event}`); + if (event.startsWith("stash:")) { + this._lastStashPageEvent = event; + } + } + } + addPageListener(eventData) { + const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData + if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){ + this._pageListeners[event] = { + regex: regex, + callBack: callBack, + manuallyHandleDispatchEvent: manuallyHandleDispatchEvent + } + return event + } else { + if (this._pageListeners[event] !== undefined) { + console.error(`Can't add page listener: Event ${event} already exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't add page listener: Event name can't start with "stash:"`) + } else { + console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`) + } + return false + } + } + removePageListener(event) { + if(event && !event?.startsWith("stash:") && this._pageListeners[event]){ + delete this._pageListeners[event] + return event + } else { + if (this._pageListeners[event] === undefined && event) { + console.error(`Can't remove page listener: Event ${event} doesn't exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't remove page listener: Event ${event} is a built in event`) + } else { + console.error(`Can't remove page listener: Missing "event" argument`) + } + return false + } + } + stopPageListener() { + clearInterval(this._pageURLCheckTimerId) + } + assignPageListeners() { + this._pageListeners = { + // scenes tab + "stash:page:scenes": { + regex: /\/scenes\?/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + "stash:page:scene:new": { + regex: /\/scenes\/new/ + }, + "stash:page:scene": { + regex: /\/scenes\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".scene-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, + + // images tab + "stash:page:images": { + regex: /\/images\?/, + handleDisplayView: true, + }, + "stash:page:image": { + regex: /\/images\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".image-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, + + // movies tab + "stash:page:movies": { + regex: /\/movies\?/, + }, + "stash:page:movie": { + regex: /\/movies\/\d+/, + }, + "stash:page:movie:scenes": { + regex: /\/movies\/\d+\?/, + callBack: () => this.processTagger() + }, + + // markers tab + "stash:page:markers": { + regex: /\/scenes\/markers/ + }, + + // galleries tab + "stash:page:galleries": { + regex: /\/galleries\?/, + handleDisplayView: true, + }, + "stash:page:gallery:new": { + regex: /\/galleries\/new/, + }, + "stash:page:gallery:images": { + regex: /\/galleries\/\d+\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, + "stash:page:gallery:add": { + regex: /\/galleries\/\d+\/add/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, + + // performers tab + "stash:page:performers": { + regex: /\/performers\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null + }, + "stash:page:performer:new": { + regex: /\/performers\/new/ + }, + "stash:page:performer": { + regex: /\/performers\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/performers\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#performer-edit", + event: "stash:page:performer:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:performer:scenes": { + regex: /\/performers\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:performer:galleries": { + regex: /\/performers\/\d+\/galleries/, + handleDisplayView: true + }, + "stash:page:performer:images": { + regex: /\/performers\/\d+\/images/, + handleDisplayView: true + }, + "stash:page:performer:movies": { + regex: /\/performers\/\d+\/movies/ + }, + "stash:page:performer:appearswith": { + regex: /\/performers\/\d+\/appearswith/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + + // studios tab + "stash:page:studios": { + regex: /\/studios\?/, + handleDisplayView: true, + }, + "stash:page:studio:new": { + regex: /\/studios\/new/ + }, + "stash:page:studio": { + regex: /\/studios\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/studios\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#studio-edit", + event: "stash:page:studio:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:studio:scenes": { + regex: /\/studios\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:studio:galleries": { + regex: /\/studios\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:studio:images": { + regex: /\/studios\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:studio:performers": { + regex: /\/studios\/\d+\/performers/, + handleDisplayView: true, + }, + "stash:page:studio:movies": { + regex: /\/studios\/\d+\/movies/ + }, + "stash:page:studio:childstudios": { + regex: /\/studios\/\d+\/childstudios/, + handleDisplayView: true, + }, + + // tags tab + "stash:page:tags": { + regex: /\/tags\?/, + handleDisplayView: true, + }, + "stash:page:tag:new": { + regex: /\/tags\/new/ + }, + "stash:page:tag": { + regex: /\/tags\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/tags\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } + + this._listenForNonPageChanges({ + selector: "#tag-edit", + event: "stash:page:tag:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:tag:scenes": { + regex: /\/tags\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:tag:galleries": { + regex: /\/tags\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:tag:images": { + regex: /\/tags\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:tag:markers": { + regex: /\/tags\/\d+\/markers/ + }, + "stash:page:tag:performers": { + regex: /\/tags\/\d+\/performers/, + handleDisplayView: true, + }, + + // settings page + "stash:page:settings": { + regex: /\/settings/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null + }, + "stash:page:settings:tasks": { + regex: /\/settings\?tab=tasks/, + callback: () => this.hidePluginTasks() + }, + "stash:page:settings:library": { + regex: /\/settings\?tab=library/ + }, + "stash:page:settings:interface": { + regex: /\/settings\?tab=interface/ + }, + "stash:page:settings:security": { + regex: /\/settings\?tab=security/ + }, + "stash:page:settings:metadata-providers": { + regex: /\/settings\?tab=metadata-providers/ + }, + "stash:page:settings:services": { + regex: /\/settings\?tab=services/ + }, + "stash:page:settings:system": { + regex: /\/settings\?tab=system/, + callBack: () => this.createSettings() + }, + "stash:page:settings:plugins": { + regex: /\/settings\?tab=plugins/ + }, + "stash:page:settings:logs": { + regex: /\/settings\?tab=logs/ + }, + "stash:page:settings:tools": { + regex: /\/settings\?tab=tools/ + }, + "stash:page:settings:changelog": { + regex: /\/settings\?tab=changelog/ + }, + "stash:page:settings:about": { + regex: /\/settings\?tab=about/ + }, + + // stats page + "stash:page:stats": { + regex: /\/stats/ + }, + + // home page + "stash:page:home": { + regex: /\/$/, + callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true}) + }, + } + } + gmMain(args) { + const events = Object.keys(this._pageListeners) + + for (const event of events) { + const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event] + + let isDisplayViewPage = false + let isListPage, isWallPage, isTaggerPage + + if (handleDisplayView) { + isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/)) + isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/)) + isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/)) + + if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true + } + + const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === "")) + + if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) { + if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event) + callBack({...args, location: window.location}, event) + } + + if (handleDisplayView) { + if (isListPage) { + this._dispatchPageEvent("stash:page:any:list", false); + this._dispatchPageEvent(event + ":list"); + } else if (isWallPage) { + this._dispatchPageEvent("stash:page:any:wall", false); + this._dispatchPageEvent(event + ":wall"); + } else if (isTaggerPage) { + this._dispatchPageEvent("stash:page:any:tagger", false); + this._dispatchPageEvent(event + ":tagger"); + } + } + } + } + addEventListeners(events, callback, ...options) { + events.forEach((event) => { + this.addEventListener(event, callback, ...options); + }); + } + hidePluginTasks() { + // hide userscript functions plugin tasks + waitForElementByXpath("//div[@id='tasks-panel']//h3[text()='Userscript Functions']/ancestor::div[contains(@class, 'setting-group')]", (elementId, el) => { + const tasks = el.querySelectorAll('.setting'); + for (const task of tasks) { + const taskName = task.querySelector('h3').innerText; + task.classList.add(this.visiblePluginTasks.indexOf(taskName) === -1 ? 'd-none' : 'd-flex'); + this.dispatchEvent(new CustomEvent('stash:plugin:task', { + 'detail': { + taskName, + task + } + })); + } + }); + } + async updateConfigValueTask(sectionKey, propName, value) { + return this.runPluginTask("userscript_functions", "Update Config Value", [{ + "key": "section_key", + "value": { + "str": sectionKey + } + }, { + "key": "prop_name", + "value": { + "str": propName + } + }, { + "key": "value", + "value": { + "str": value + } + }]); + } + async getConfigValueTask(sectionKey, propName) { + await this.runPluginTask("userscript_functions", "Get Config Value", [{ + "key": "section_key", + "value": { + "str": sectionKey + } + }, { + "key": "prop_name", + "value": { + "str": propName + } + }]); + + // poll logs until plugin task output appears + const prefix = `[Plugin / Userscript Functions] get_config_value: [${sectionKey}][${propName}] =`; + return this.pollLogsForMessage(prefix); + } + async pollLogsForMessage(prefix) { + const reqTime = Date.now(); + const reqData = { + "variables": {}, + "query": `query Logs { + logs { + time + level + message + } + }` + }; + await new Promise(r => setTimeout(r, 500)); + let retries = 0; + while (true) { + const delay = 2 ** retries * 100; + await new Promise(r => setTimeout(r, delay)); + retries++; + + const logs = await this.callGQL(reqData); + for (const log of logs.data.logs) { + const logTime = Date.parse(log.time); + if (logTime > reqTime && log.message.startsWith(prefix)) { + return log.message.replace(prefix, '').trim(); + } + } + + if (retries >= 5) { + throw `Poll logs failed for message: ${prefix}`; + } + } + } + processTagger() { + waitForElementByXpath("//button[text()='Scrape All']", (xpath, el) => { + this.dispatchEvent(new CustomEvent('tagger', { + 'detail': el + })); + + const searchItemContainer = document.querySelector('.tagger-container').lastChild; + + const observer = new MutationObserver(mutations => { + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Performer:')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:remoteperformer', { + 'detail': { + node, + mutation + } + })); + } else if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Studio:')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:remotestudio', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'SPAN' && node.innerText.startsWith('Matched:')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:local', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'UL') { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:container', { + 'detail': { + node, + mutation + } + })); + } else if (node?.classList?.contains('col-lg-6')) { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:subcontainer', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'H5') { // scene date + this.dispatchEvent(new CustomEvent('tagger:mutation:add:date', { + 'detail': { + node, + mutation + } + })); + } else if (node.tagName === 'DIV' && node?.classList?.contains('d-flex') && node?.classList?.contains('flex-column')) { // scene stashid, url, details + this.dispatchEvent(new CustomEvent('tagger:mutation:add:detailscontainer', { + 'detail': { + node, + mutation + } + })); + } else { + this.dispatchEvent(new CustomEvent('tagger:mutation:add:other', { + 'detail': { + node, + mutation + } + })); + } + }); + }); + this.dispatchEvent(new CustomEvent('tagger:mutations:searchitems', { + 'detail': mutations + })); + }); + observer.observe(searchItemContainer, { + childList: true, + subtree: true + }); + + const taggerContainerHeader = document.querySelector('.tagger-container-header'); + const taggerContainerHeaderObserver = new MutationObserver(mutations => { + this.dispatchEvent(new CustomEvent('tagger:mutations:header', { + 'detail': mutations + })); + }); + taggerContainerHeaderObserver.observe(taggerContainerHeader, { + childList: true, + subtree: true + }); + + for (const searchItem of document.querySelectorAll('.search-item')) { + this.dispatchEvent(new CustomEvent('tagger:searchitem', { + 'detail': searchItem + })); + } + + if (!document.getElementById('progress-bar')) { + const progressBar = createElementFromHTML(`
`); + progressBar.classList.add('progress'); + progressBar.style.display = 'none'; + taggerContainerHeader.appendChild(progressBar); + } + }); + waitForElementByXpath("//div[@class='tagger-container-header']/div/div[@class='row']/h4[text()='Configuration']", (xpath, el) => { + this.dispatchEvent(new CustomEvent('tagger:configuration', { + 'detail': el + })); + }); + } + setProgress(value) { + const progressBar = document.getElementById('progress-bar'); + if (progressBar) { + progressBar.firstChild.style.width = value + '%'; + progressBar.style.display = (value <= 0 || value > 100) ? 'none' : 'flex'; + } + } + processRemoteScenes(data) { + if (data.data?.scrapeMultiScenes) { + for (const matchResults of data.data.scrapeMultiScenes) { + for (const scene of matchResults) { + this.remoteScenes[scene.remote_site_id] = scene; + } + } + } else if (data.data?.scrapeSingleScene) { + for (const scene of data.data.scrapeSingleScene) { + this.remoteScenes[scene.remote_site_id] = scene; + } + } + } + processScene(data) { + if (data.data.findScene) { + this.scenes[data.data.findScene.id] = data.data.findScene; + } + } + processScenes(data) { + if (data.data.findScenes?.scenes) { + for (const scene of data.data.findScenes.scenes) { + this.scenes[scene.id] = scene; + } + } + } + processStudios(data) { + if (data.data.findStudios?.studios) { + for (const studio of data.data.findStudios.studios) { + this.studios[studio.id] = studio; + } + } + } + processPerformers(data) { + if (data.data.findPerformers?.performers) { + for (const performer of data.data.findPerformers.performers) { + this.performers[performer.id] = performer; + } + } + } + processApiKey(data) { + if (data.data.generateAPIKey != null && this.pluginVersion) { + this.updateConfigValueTask('STASH', 'api_key', data.data.generateAPIKey); + } + } + parseSearchItem(searchItem) { + const urlNode = searchItem.querySelector('a.scene-link'); + const url = new URL(urlNode.href); + const id = url.pathname.replace('/scenes/', ''); + const data = this.scenes[id]; + const nameNode = searchItem.querySelector('a.scene-link > div.TruncatedText'); + const name = nameNode.innerText; + const queryInput = searchItem.querySelector('input.text-input'); + const performerNodes = searchItem.querySelectorAll('.performer-tag-container'); + + return { + urlNode, + url, + id, + data, + nameNode, + name, + queryInput, + performerNodes + } + } + parseSearchResultItem(searchResultItem) { + const remoteUrlNode = searchResultItem.querySelector('.scene-details .optional-field .optional-field-content a'); + const remoteId = remoteUrlNode?.href.split('/').pop(); + const remoteUrl = remoteUrlNode?.href ? new URL(remoteUrlNode.href) : null; + const remoteData = this.remoteScenes[remoteId]; + + const sceneDetailNodes = searchResultItem.querySelectorAll('.scene-details .optional-field .optional-field-content'); + let urlNode = null; + let detailsNode = null; + for (const sceneDetailNode of sceneDetailNodes) { + if (sceneDetailNode.innerText.startsWith('http') && (remoteUrlNode?.href !== sceneDetailNode.innerText)) { + urlNode = sceneDetailNode; + } else if (!sceneDetailNode.innerText.startsWith('http')) { + detailsNode = sceneDetailNode; + } + } + + const imageNode = searchResultItem.querySelector('.scene-image-container .optional-field .optional-field-content'); + + const metadataNode = searchResultItem.querySelector('.scene-metadata'); + const titleNode = metadataNode.querySelector('h4 .optional-field .optional-field-content'); + const codeAndDateNodes = metadataNode.querySelectorAll('h5 .optional-field .optional-field-content'); + let codeNode = null; + let dateNode = null; + for (const node of codeAndDateNodes) { + if (node.textContent.includes('-')) { + dateNode = node; + } else { + codeNode = node; + } + } + + const entityNodes = searchResultItem.querySelectorAll('.entity-name'); + let studioNode = null; + const performerNodes = []; + for (const entityNode of entityNodes) { + if (entityNode.innerText.startsWith('Studio:')) { + studioNode = entityNode; + } else if (entityNode.innerText.startsWith('Performer:')) { + performerNodes.push(entityNode); + } + } + + const matchNodes = searchResultItem.querySelectorAll('div.col-lg-6 div.mt-2 div.row.no-gutters.my-2 span.ml-auto'); + const matches = [] + for (const matchNode of matchNodes) { + let matchType = null; + const entityNode = matchNode.parentElement.querySelector('.entity-name'); + + const matchName = matchNode.querySelector('.optional-field-content b').innerText; + const remoteName = entityNode.querySelector('b').innerText; + + let data; + if (entityNode.innerText.startsWith('Performer:')) { + matchType = 'performer'; + if (remoteData) { + data = remoteData.performers.find(performer => performer.name === remoteName); + } + } else if (entityNode.innerText.startsWith('Studio:')) { + matchType = 'studio'; + if (remoteData) { + data = remoteData.studio + } + } + + matches.push({ + matchType, + matchNode, + entityNode, + matchName, + remoteName, + data + }); + } + + return { + remoteUrlNode, + remoteId, + remoteUrl, + remoteData, + urlNode, + detailsNode, + imageNode, + titleNode, + codeNode, + dateNode, + studioNode, + performerNodes, + matches + } + } +} + +window.stash = new Stash(); + +function waitForElementQuerySelector(query, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.querySelector(query); + if (element) { + callBack(query, element); + } else { + waitForElementQuerySelector(query, callBack, time); + } + }, time); +} + +function waitForElementClass(elementId, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.getElementsByClassName(elementId); + if (element.length > 0) { + callBack(elementId, element); + } else { + waitForElementClass(elementId, callBack, time); + } + }, time); +} + +function waitForElementId(elementId, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = document.getElementById(elementId); + if (element != null) { + callBack(elementId, element); + } else { + waitForElementId(elementId, callBack, time); + } + }, time); +} + +function waitForElementByXpath(xpath, callBack, time) { + time = (typeof time !== 'undefined') ? time : 100; + window.setTimeout(() => { + const element = getElementByXpath(xpath); + if (element) { + callBack(xpath, element); + } else { + waitForElementByXpath(xpath, callBack, time); + } + }, time); +} + +function getElementByXpath(xpath, contextNode) { + return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +function createElementFromHTML(htmlString) { + const div = document.createElement('div'); + div.innerHTML = htmlString.trim(); + + // Change this to div.childNodes to support multiple top-level nodes. + return div.firstChild; +} + +function getElementByXpath(xpath, contextNode) { + return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +function getElementsByXpath(xpath, contextNode) { + return document.evaluate(xpath, contextNode || document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); +} + +function getClosestAncestor(el, selector, stopSelector) { + let retval = null; + while (el) { + if (el.matches(selector)) { + retval = el; + break + } else if (stopSelector && el.matches(stopSelector)) { + break + } + el = el.parentElement; + } + return retval; +} + +function setNativeValue(element, value) { + const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set; + const prototype = Object.getPrototypeOf(element); + const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set; + + if (valueSetter && valueSetter !== prototypeValueSetter) { + prototypeValueSetter.call(element, value); + } else { + valueSetter.call(element, value); + } +} + +function updateTextInput(element, value) { + setNativeValue(element, value); + element.dispatchEvent(new Event('input', { + bubbles: true + })); +} + +function concatRegexp(reg, exp) { + let flags = reg.flags + exp.flags; + flags = Array.from(new Set(flags.split(''))).join(); + return new RegExp(reg.source + exp.source, flags); +} + +function sortElementChildren(node) { + const items = node.childNodes; + const itemsArr = []; + for (const i in items) { + if (items[i].nodeType == Node.ELEMENT_NODE) { // get rid of the whitespace text nodes + itemsArr.push(items[i]); + } + } + + itemsArr.sort((a, b) => { + return a.innerHTML == b.innerHTML ? + 0 : + (a.innerHTML > b.innerHTML ? 1 : -1); + }); + + for (let i = 0; i < itemsArr.length; i++) { + node.appendChild(itemsArr[i]); + } +} + +function xPathResultToArray(result) { + let node = null; + const nodes = []; + while (node = result.iterateNext()) { + nodes.push(node); + } + return nodes; +} + +function createStatElement(container, title, heading) { + const statEl = document.createElement('div'); + statEl.classList.add('stats-element'); + container.appendChild(statEl); + + const statTitle = document.createElement('p'); + statTitle.classList.add('title'); + statTitle.innerText = title; + statEl.appendChild(statTitle); + + const statHeading = document.createElement('p'); + statHeading.classList.add('heading'); + statHeading.innerText = heading; + statEl.appendChild(statHeading); +} + +const reloadImg = url => + fetch(url, { + cache: 'reload', + mode: 'no-cors' + }) + .then(() => document.body.querySelectorAll(`img[src='${url}']`) + .forEach(img => img.src = url)); diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 6e2ee885..79152106 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -45,23 +45,41 @@ class Stash extends EventTarget { this._pageUrlCheckInterval = pageUrlCheckInterval; this._detectReRenders = detectReRenders; this.fireOnHashChangesToo = true; - waitForElementQuerySelector(this._detectReRenders ? ".main > div" : "html", () => { - this.pageURLCheckTimer = setInterval(() => { + this._lastPathStr = ""; + this._lastQueryStr = ""; + this._lastHashStr = ""; + this._lastHref = ""; + this._lastStashPageEvent = ""; + this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => { + this._pageURLCheckTimerId = setInterval(() => { // Loop every 100 ms - if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (this.fireOnHashChangesToo && this.lastHashStr !== location.hash) || this.lastHref !== location.href || (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders)) { - this.log.debug('[Navigation] Page Changed'); - this.dispatchEvent(new Event('page')); - this.gmMain(false, this.lastHref); - this.lastPathStr = location.pathname; - this.lastQueryStr = location.search; - this.lastHashStr = location.hash; - this.lastHref = location.href; - if (this._detectReRenders) waitForElementQuerySelector(".main > div", (query, element) => { - element.setAttribute("stashUserscriptLibrary", ""); - }, 10) + if ( + this._lastPathStr !== location.pathname || + this._lastQueryStr !== location.search || + (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) || + this._lastHref !== location.href || + (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders) + ) { + this._dispatchPageEvent("stash:page", false) + this.gmMain({ + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); + this._lastPathStr = location.pathname + this._lastQueryStr = location.search + this._lastHashStr = location.hash + this._lastHref = location.href + if (this._detectReRenders) { + this.waitForElement(".main > div", 10000).then((element) => { + element.setAttribute("stashUserscriptLibrary", ""); + }) + } } }, this._pageUrlCheckInterval); - }, 1) + }) stashListener.addEventListener('response', (evt) => { if (evt.detail.data?.plugins) { this.getPluginVersion(evt.detail); @@ -96,17 +114,18 @@ class Stash extends EventTarget { this.studios = {}; this.performers = {}; this.userscripts = []; + this._pageListeners = {}; + this.assignPageListeners() } async getVersion() { const reqData = { "operationName": "", "variables": {}, "query": `query version { - version { - version - } -} -` + version { + version + } + }` }; const data = await this.callGQL(reqData); const versionString = data.data.version.version; @@ -267,10 +286,10 @@ class Stash extends EventTarget { }; return this.callGQL(reqData); } - matchUrl(location, fragment) { - const regexp = concatRegexp(new RegExp(location.origin), fragment); + matchUrl(href, fragment) { + const regexp = concatRegexp(new RegExp(window.location.origin), fragment); this.log.debug(regexp, location.href.match(regexp)); - return location.href.match(regexp) != null; + return href.match(regexp) != null; } createSettings() { waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => { @@ -421,9 +440,9 @@ class Stash extends EventTarget { if (disconnectOnPageChange) { function disconnect() { observer.disconnect() - stash.removeEventListener("page", disconnect) + stash.removeEventListener("stash:page", disconnect) } - this.addEventListener("page", disconnect) + stash.addEventListener("stash:page", disconnect) } }) } @@ -445,14 +464,14 @@ class Stash extends EventTarget { if (disconnectOnPageChange) { function disconnect() { observer.disconnect() - stash.removeEventListener("page", disconnect) + stash.removeEventListener("stash:page", disconnect) } - this.addEventListener("page", disconnect) + stash.addEventListener("stash:page", disconnect) } }) } - async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", eventMessage = "", isRecursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ - if (isRecursive) return + async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ + if (recursive) return if (listenType === "tabs") { const locationElement = await this.waitForElement(location, 10000, document.body, true) const stash = this @@ -463,386 +482,431 @@ class Stash extends EventTarget { const tagName = clickedChild.getAttribute("data-rb-event-key") const parentEvent = tagName.split("-")[0] const childEvent = tagName.split("-").slice(1, -1).join("-") - event = `page:${parentEvent}:${childEvent}` + event = `stash:page:${parentEvent}:${childEvent}` if (previousEvent === event || !condition()) return previousEvent = event - stash.log.debug("[Navigation] " + `${parentEvent[0].toUpperCase() + parentEvent.slice(1)} Page - ${childEvent[0].toUpperCase() + childEvent.slice(1)}`); - stash.dispatchEvent(new Event(event)); + stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false) + stash._dispatchPageEvent(event) } if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) locationElement.addEventListener("click", listenForTabClicks); function removeEventListenerOnPageChange() { locationElement.removeEventListener("click", listenForTabClicks) - stash.removeEventListener("page", removeEventListenerOnPageChange) + stash.removeEventListener("stash:page", removeEventListenerOnPageChange) } - stash.addEventListener("page", removeEventListenerOnPageChange) + stash.addEventListener("stash:page", removeEventListenerOnPageChange) } else if (await this.waitForElement(selector, null, location, true)) { - this.log.debug("[Navigation] " + eventMessage); - this.dispatchEvent(new Event(event)); + this._dispatchPageEvent(event) if (await this.waitForElementDeath(selector, location, true)) { - if (this.lastPathStr === window.location.pathname && !reRunGmMain) { - this._listenForNonPageChanges({selector: selector, event: event, eventMessage: eventMessage}) - } else if (this.lastPathStr === window.location.pathname && reRunGmMain) { - this.gmMain(true, this.lastHref) + if (this._lastPathStr === window.location.pathname && !reRunGmMain) { + await this._listenForNonPageChanges({selector: selector, event: event}) + } else if (this._lastPathStr === window.location.pathname && reRunGmMain) { + this.gmMain({ + recursive: true, + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, + }); } } } callback() } - gmMain(isRecursive = false, lastHref) { - const location = window.location; - this.log.debug(URL, window.location); - - // Run parent page specific functions - // detect performer edit page - if (this.matchUrl(location, /\/performers\/\d+/)) { - if(!new RegExp(/\/performers\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Performer Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:performer')); - } - - this._listenForNonPageChanges({selector: "#performer-edit", event: "page:performer:edit", eventMessage: "Performer Page - Edit", reRunGmMain: true, callback: () => { - if (this._detectReRenders) { - this.log.debug('[Navigation] Performer Page'); - this.dispatchEvent(new Event('page:performer')); + _dispatchPageEvent(event, addToHistory = true) { + this.dispatchEvent(new CustomEvent(event, { + detail: { +event: event, + lastEventState: { + lastPathStr: this._lastPathStr, + lastQueryStr: this._lastQueryStr, + lastHashStr: this._lastHashStr, + lastHref: this._lastHref, + lastStashPageEvent: this._lastStashPageEvent, } - }}) + } + })) + if (addToHistory) { + this.log.debug(`[Navigation] ${event}`); + if (event.startsWith("stash:")) { + this._lastStashPageEvent = event; + } } - // detect studio edit page - else if (this.matchUrl(location, /\/studios\/\d+/)) { - if(!new RegExp(/\/studios\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Studio Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:studio')); + } + addPageListener(eventData) { + const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData + if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){ + this._pageListeners[event] = { + regex: regex, + callBack: callBack, + manuallyHandleDispatchEvent: manuallyHandleDispatchEvent } - - this._listenForNonPageChanges({selector: "#studio-edit", event: "page:studio:edit", eventMessage: "Studio Page - Edit", reRunGmMain: true, callback: () => { - if (this._detectReRenders) { - this.log.debug('[Navigation] Studio Page'); - this.dispatchEvent(new Event('page:studio')); - } - }}) + return event + } else { + if (this._pageListeners[event] !== undefined) { + console.error(`Can't add page listener: Event ${event} already exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't add page listener: Event name can't start with "stash:"`) + } else { + console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`) + } + return false } - // detect tag edit page - else if (this.matchUrl(location, /\/tags\/\d+/)) { - if(!new RegExp(/\/tags\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Tag Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:tag')); + } + removePageListener(event) { + if(event && !event?.startsWith("stash:") && this._pageListeners[event]){ + delete this._pageListeners[event] + return event + } else { + if (this._pageListeners[event] === undefined && event) { + console.error(`Can't remove page listener: Event ${event} doesn't exists`) + } else if (event?.startsWith("stash:")) { + console.error(`Can't remove page listener: Event ${event} is a built in event`) + } else { + console.error(`Can't remove page listener: Missing "event" argument`) } - - this._listenForNonPageChanges({selector: "#tag-edit", event: "page:tag:edit", eventMessage: "Tag Page - Edit", reRunGmMain: true, callback: () => { - if (this._detectReRenders) { - this.log.debug('[Navigation] Tag Page'); - this.dispatchEvent(new Event('page:tag')); - } - }}) + return false } + } + stopPageListener() { + clearInterval(this._pageURLCheckTimerId) + } + assignPageListeners() { + this._pageListeners = { + // scenes tab + "stash:page:scenes": { + regex: /\/scenes\?/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, + "stash:page:scene:new": { + regex: /\/scenes\/new/ + }, + "stash:page:scene": { + regex: /\/scenes\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".scene-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, - // markers page - if (this.matchUrl(location, /\/scenes\/markers/)) { - this.log.debug('[Navigation] Markers Page'); - this.dispatchEvent(new Event('page:markers')); - } - // create scene page - else if (this.matchUrl(location, /\/scenes\/new/)) { - this.log.debug('[Navigation] Create Scene Page'); - this.dispatchEvent(new Event('page:scene:new')); - } - // scene page - else if (this.matchUrl(location, /\/scenes\/\d+/)) { - this.log.debug('[Navigation] Scene Page'); - this.dispatchEvent(new Event('page:scene')); - - this._listenForNonPageChanges({ - location: ".scene-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive - }) - } - // scenes page - else if (this.matchUrl(location, /\/scenes\?/)) { - this.log.debug('[Navigation] Scenes Page'); - this.processTagger(); - this.dispatchEvent(new Event('page:scenes')); - } + // images tab + "stash:page:images": { + regex: /\/images\?/, + handleDisplayView: true, + }, + "stash:page:image": { + regex: /\/images\/\d+/, + callBack: ({recursive = false}) => this._listenForNonPageChanges({ + location: ".image-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive + }) + }, + + // movies tab + "stash:page:movies": { + regex: /\/movies\?/, + }, + "stash:page:movie": { + regex: /\/movies\/\d+/, + }, + "stash:page:movie:scenes": { + regex: /\/movies\/\d+\?/, + callBack: () => this.processTagger() + }, - // image page - else if (this.matchUrl(location, /\/images\/\d+/)) { - this.log.debug('[Navigation] Image Page'); - this.dispatchEvent(new Event('page:image')); + // markers tab + "stash:page:markers": { + regex: /\/scenes\/markers/ + }, - this._listenForNonPageChanges({ - location: ".image-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive - }) - } - // images page - else if (this.matchUrl(location, /\/images\?/)) { - this.log.debug('[Navigation] Images Page'); - this.dispatchEvent(new Event('page:images')); - } + // galleries tab + "stash:page:galleries": { + regex: /\/galleries\?/, + handleDisplayView: true, + }, + "stash:page:gallery:new": { + regex: /\/galleries\/new/, + }, + "stash:page:gallery:images": { + regex: /\/galleries\/\d+\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, + "stash:page:gallery:add": { + regex: /\/galleries\/\d+\/add/, + manuallyHandleDispatchEvent: true, + handleDisplayView: "ignoreDisplayViewCondition", + callBack: ({lastHref, recursive = false}, event) => { + if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ + this._dispatchPageEvent("stash:page:gallery"); + this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) + } + + this._dispatchPageEvent(event); + + this._listenForNonPageChanges({ + location: ".gallery-tabs .nav-tabs", + listenType: "tabs", + recursive: recursive, + listenDefaultTab: false + }) + } + }, - // movie scenes page - else if (this.matchUrl(location, /\/movies\/\d+\?/)) { - this.log.debug('[Navigation] Movie Page - Scenes'); - this.processTagger(); - this.dispatchEvent(new Event('page:movie:scenes')); - } - // movie page - else if (this.matchUrl(location, /\/movies\/\d+/)) { - this.log.debug('[Navigation] Movie Page'); - this.dispatchEvent(new Event('page:movie')); - } - // movies page - else if (this.matchUrl(location, /\/movies\?/)) { - this.log.debug('[Navigation] Movies Page'); - this.dispatchEvent(new Event('page:movies')); - } + // performers tab + "stash:page:performers": { + regex: /\/performers\?/, + manuallyHandleDispatchEvent: true, + handleDisplayView: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null + }, + "stash:page:performer:new": { + regex: /\/performers\/new/ + }, + "stash:page:performer": { + regex: /\/performers\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/performers\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } - // create gallery page - else if (this.matchUrl(location, /\/galleries\/new/)) { - this.log.debug('[Navigation] Create Gallery Page'); - this.dispatchEvent(new Event('page:gallery:new')); + this._listenForNonPageChanges({ + selector: "#performer-edit", + event: "stash:page:performer:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) } - // gallery add page - else if (this.matchUrl(location, /\/galleries\/\d+\/add/)) { - if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Gallery Page'); - this.dispatchEvent(new Event('page:gallery')); - this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"}) - } + }, + "stash:page:performer:scenes": { + regex: /\/performers\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:performer:galleries": { + regex: /\/performers\/\d+\/galleries/, + handleDisplayView: true + }, + "stash:page:performer:images": { + regex: /\/performers\/\d+\/images/, + handleDisplayView: true + }, + "stash:page:performer:movies": { + regex: /\/performers\/\d+\/movies/ + }, + "stash:page:performer:appearswith": { + regex: /\/performers\/\d+\/appearswith/, + handleDisplayView: true, + callBack: () => this.processTagger() + }, - this.log.debug('[Navigation] Gallery Page - Add'); - this.dispatchEvent(new Event('page:gallery:add')); - - this._listenForNonPageChanges({ - location: ".gallery-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive, - listenDefaultTab: false - }) - } - // gallery page - else if (this.matchUrl(location, /\/galleries\/\d+/)) { - if(!new RegExp(/\/galleries\/\d+/).test(lastHref)){ - this.log.debug('[Navigation] Gallery Page'); - this.dispatchEvent(new Event('page:gallery')); - this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "page:gallery:details", eventMessage: "Gallery Page - Details"}) - } + // studios tab + "stash:page:studios": { + regex: /\/studios\?/, + handleDisplayView: true, + }, + "stash:page:studio:new": { + regex: /\/studios\/new/ + }, + "stash:page:studio": { + regex: /\/studios\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/studios\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } - this.log.debug('[Navigation] Gallery Page - Images'); - this.dispatchEvent(new Event('page:gallery:images')); + this._listenForNonPageChanges({ + selector: "#studio-edit", + event: "stash:page:studio:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:studio:scenes": { + regex: /\/studios\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:studio:galleries": { + regex: /\/studios\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:studio:images": { + regex: /\/studios\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:studio:performers": { + regex: /\/studios\/\d+\/performers/, + handleDisplayView: true, + }, + "stash:page:studio:movies": { + regex: /\/studios\/\d+\/movies/ + }, + "stash:page:studio:childstudios": { + regex: /\/studios\/\d+\/childstudios/, + handleDisplayView: true, + }, - this._listenForNonPageChanges({ - location: ".gallery-tabs .nav-tabs", - listenType: "tabs", - isRecursive: isRecursive, - listenDefaultTab: false - }) - } - // galleries page - else if (this.matchUrl(location, /\/galleries\?/)) { - this.log.debug('[Navigation] Galleries Page'); - this.dispatchEvent(new Event('page:galleries')); - } + // tags tab + "stash:page:tags": { + regex: /\/tags\?/, + handleDisplayView: true, + }, + "stash:page:tag:new": { + regex: /\/tags\/new/ + }, + "stash:page:tag": { + regex: /\/tags\/\d+/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => { + if(!this.matchUrl(lastHref, /\/tags\/\d+/)){ + this._dispatchPageEvent(event); + this.processTagger(); + } - // performer galleries page - else if (this.matchUrl(location, /\/performers\/\d+\/galleries/)) { - this.log.debug('[Navigation] Performer Page - Galleries'); - this.dispatchEvent(new Event('page:performer:galleries')); - } - // performer images page - else if (this.matchUrl(location, /\/performers\/\d+\/images/)) { - this.log.debug('[Navigation] Performer Page - Images'); - this.dispatchEvent(new Event('page:performer:images')); - } - // performer movies page - else if (this.matchUrl(location, /\/performers\/\d+\/movies/)) { - this.log.debug('[Navigation] Performer Page - Movies'); - this.dispatchEvent(new Event('page:performer:movies')); - } - // performer appearswith page - else if (this.matchUrl(location, /\/performers\/\d+\/appearswith/)) { - this.log.debug('[Navigation] Performer Page - Appears With'); - this.processTagger(); - this.dispatchEvent(new Event('page:performer:appearswith')); - } - // create performer page - else if (this.matchUrl(location, /\/performers\/new/)) { - this.log.debug('[Navigation] Create Performer Page'); - this.dispatchEvent(new Event('page:performer:new')); - } - // performer scenes page - else if (this.matchUrl(location, /\/performers\/\d+\?/)) { - this.log.debug('[Navigation] Performer Page - Scenes'); - this.dispatchEvent(new Event('page:performer:scenes')); - } - // performers page - else if (this.matchUrl(location, /\/performers\?/)) { - this.log.debug('[Navigation] Performers Page'); - this.dispatchEvent(new Event('page:performers')); - } + this._listenForNonPageChanges({ + selector: "#tag-edit", + event: "stash:page:tag:edit", + reRunGmMain: true, + callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null + }) + } + }, + "stash:page:tag:scenes": { + regex: /\/tags\/\d+\?/, + handleDisplayView: true, + }, + "stash:page:tag:galleries": { + regex: /\/tags\/\d+\/galleries/, + handleDisplayView: true, + }, + "stash:page:tag:images": { + regex: /\/tags\/\d+\/images/, + handleDisplayView: true, + }, + "stash:page:tag:markers": { + regex: /\/tags\/\d+\/markers/ + }, + "stash:page:tag:performers": { + regex: /\/tags\/\d+\/performers/, + handleDisplayView: true, + }, - // studio galleries page - else if (this.matchUrl(location, /\/studios\/\d+\/galleries/)) { - this.log.debug('[Navigation] Studio Page - Galleries'); - this.dispatchEvent(new Event('page:studio:galleries')); - } - // studio images page - else if (this.matchUrl(location, /\/studios\/\d+\/images/)) { - this.log.debug('[Navigation] Studio Page - Images'); - this.dispatchEvent(new Event('page:studio:images')); - } - // studio performers page - else if (this.matchUrl(location, /\/studios\/\d+\/performers/)) { - this.log.debug('[Navigation] Studio Page - Performers'); - this.dispatchEvent(new Event('page:studio:performers')); - } - // studio movies page - else if (this.matchUrl(location, /\/studios\/\d+\/movies/)) { - this.log.debug('[Navigation] Studio Page - Movies'); - this.dispatchEvent(new Event('page:studio:movies')); - } - // studio childstudios page - else if (this.matchUrl(location, /\/studios\/\d+\/childstudios/)) { - this.log.debug('[Navigation] Studio Page - Child Studios'); - this.dispatchEvent(new Event('page:studio:childstudios')); - } - // create studio page - else if (this.matchUrl(location, /\/studios\/new/)) { - this.log.debug('[Navigation] Create Studio Page'); - this.dispatchEvent(new Event('page:studio:new')); - } - // studio scenes page - else if (this.matchUrl(location, /\/studios\/\d+\?/)) { - this.log.debug('[Navigation] Studio Page - Scenes'); - this.dispatchEvent(new Event('page:studio:scenes')); - } - // studios page - else if (this.matchUrl(location, /\/studios\?/)) { - this.log.debug('[Navigation] Studios Page'); - this.dispatchEvent(new Event('page:studios')); - } + // settings page + "stash:page:settings": { + regex: /\/settings/, + manuallyHandleDispatchEvent: true, + callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null + }, + "stash:page:settings:tasks": { + regex: /\/settings\?tab=tasks/, + callback: () => this.hidePluginTasks() + }, + "stash:page:settings:library": { + regex: /\/settings\?tab=library/ + }, + "stash:page:settings:interface": { + regex: /\/settings\?tab=interface/ + }, + "stash:page:settings:security": { + regex: /\/settings\?tab=security/ + }, + "stash:page:settings:metadata-providers": { + regex: /\/settings\?tab=metadata-providers/ + }, + "stash:page:settings:services": { + regex: /\/settings\?tab=services/ + }, + "stash:page:settings:system": { + regex: /\/settings\?tab=system/, + callBack: () => this.createSettings() + }, + "stash:page:settings:plugins": { + regex: /\/settings\?tab=plugins/ + }, + "stash:page:settings:logs": { + regex: /\/settings\?tab=logs/ + }, + "stash:page:settings:tools": { + regex: /\/settings\?tab=tools/ + }, + "stash:page:settings:changelog": { + regex: /\/settings\?tab=changelog/ + }, + "stash:page:settings:about": { + regex: /\/settings\?tab=about/ + }, - // tag galleries page - else if (this.matchUrl(location, /\/tags\/\d+\/galleries/)) { - this.log.debug('[Navigation] Tag Page - Galleries'); - this.dispatchEvent(new Event('page:tag:galleries')); - } - // tag images page - else if (this.matchUrl(location, /\/tags\/\d+\/images/)) { - this.log.debug('[Navigation] Tag Page - Images'); - this.dispatchEvent(new Event('page:tag:images')); - } - // tag markers page - else if (this.matchUrl(location, /\/tags\/\d+\/markers/)) { - this.log.debug('[Navigation] Tag Page - Markers'); - this.dispatchEvent(new Event('page:tag:markers')); - } - // tag performers page - else if (this.matchUrl(location, /\/tags\/\d+\/performers/)) { - this.log.debug('[Navigation] Tag Page - Performers'); - this.dispatchEvent(new Event('page:tag:performers')); - } - // create tag page - else if (this.matchUrl(location, /\/tags\/new/)) { - this.log.debug('[Navigation] Create Tag Page'); - this.dispatchEvent(new Event('page:tag:new')); - } - // tag scenes page - else if (this.matchUrl(location, /\/tags\/\d+\?/)) { - this.log.debug('[Navigation] Tag Page - Scenes'); - this.dispatchEvent(new Event('page:tag:scenes')); - } - // tags page - else if (this.matchUrl(location, /\/tags\?/)) { - this.log.debug('[Navigation] Tags Page'); - this.dispatchEvent(new Event('page:tags')); + // stats page + "stash:page:stats": { + regex: /\/stats/ + }, + + // home page + "stash:page:home": { + regex: /\/$/, + callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true}) + }, } + } + gmMain(args) { + const events = Object.keys(this._pageListeners) - // settings page tasks tab - else if (this.matchUrl(location, /\/settings\?tab=tasks/)) { - if(!new RegExp(/\/settings\?/).test(lastHref)){ - this.log.debug('[Navigation] Settings Page'); - this.dispatchEvent(new Event('page:settings')); - this.hidePluginTasks(); + for (const event of events) { + const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event] + + let isDisplayViewPage = false + let isListPage, isWallPage, isTaggerPage + + if (handleDisplayView) { + isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/)) + isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/)) + isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/)) + + if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true } - this.log.debug('[Navigation] Settings Page Tasks Tab'); - this.dispatchEvent(new Event('page:settings:tasks')); - } - // settings page library tab - else if (this.matchUrl(location, /\/settings\?tab=library/)) { - this.log.debug('[Navigation] Settings Page Library Tab'); - this.dispatchEvent(new Event('page:settings:library')); - } - // settings page interface tab - else if (this.matchUrl(location, /\/settings\?tab=interface/)) { - this.log.debug('[Navigation] Settings Page Interface Tab'); - this.dispatchEvent(new Event('page:settings:interface')); - } - // settings page security tab - else if (this.matchUrl(location, /\/settings\?tab=security/)) { - this.log.debug('[Navigation] Settings Page Security Tab'); - this.dispatchEvent(new Event('page:settings:security')); - } - // settings page metadata providers tab - else if (this.matchUrl(location, /\/settings\?tab=metadata-providers/)) { - this.log.debug('[Navigation] Settings Page Metadata Providers Tab'); - this.dispatchEvent(new Event('page:settings:metadata-providers')); - } - // settings page services tab - else if (this.matchUrl(location, /\/settings\?tab=services/)) { - this.log.debug('[Navigation] Settings Page Services Tab'); - this.dispatchEvent(new Event('page:settings:services')); - } - // settings page system tab - else if (this.matchUrl(location, /\/settings\?tab=system/)) { - this.log.debug('[Navigation] Settings Page System Tab'); - this.createSettings(); - this.dispatchEvent(new Event('page:settings:system')); - } - // settings page plugins tab - else if (this.matchUrl(location, /\/settings\?tab=plugins/)) { - this.log.debug('[Navigation] Settings Page Plugins Tab'); - this.dispatchEvent(new Event('page:settings:plugins')); - } - // settings page logs tab - else if (this.matchUrl(location, /\/settings\?tab=logs/)) { - this.log.debug('[Navigation] Settings Page Logs Tab'); - this.dispatchEvent(new Event('page:settings:logs')); - } - // settings page tools tab - else if (this.matchUrl(location, /\/settings\?tab=tools/)) { - this.log.debug('[Navigation] Settings Page Tools Tab'); - this.dispatchEvent(new Event('page:settings:tools')); - } - // settings page changelog tab - else if (this.matchUrl(location, /\/settings\?tab=changelog/)) { - this.log.debug('[Navigation] Settings Page Changelog Tab'); - this.dispatchEvent(new Event('page:settings:changelog')); - } - // settings page about tab - else if (this.matchUrl(location, /\/settings\?tab=about/)) { - this.log.debug('[Navigation] Settings Page About Tab'); - this.dispatchEvent(new Event('page:settings:about')); - } - - // stats page - else if (this.matchUrl(location, /\/stats/)) { - this.log.debug('[Navigation] Stats Page'); - this.dispatchEvent(new Event('page:stats')); - } + const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === "")) - // home page - else if (this.matchUrl(location, /\/$/)) { - this.log.debug('[Navigation] Home Page'); - this.dispatchEvent(new Event('page:home')); + if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) { + if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event) + callBack({...args, location: window.location}, event) + } - this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "page:home:edit", eventMessage: "Home Page - Edit", reRunGmMain: true}) + if (handleDisplayView) { + if (isListPage) { + this._dispatchPageEvent("stash:page:any:list", false); + this._dispatchPageEvent(event + ":list"); + } else if (isWallPage) { + this._dispatchPageEvent("stash:page:any:wall", false); + this._dispatchPageEvent(event + ":wall"); + } else if (isTaggerPage) { + this._dispatchPageEvent("stash:page:any:tagger", false); + this._dispatchPageEvent(event + ":tagger"); + } + } } } addEventListeners(events, callback, ...options) { diff --git a/plugins/stats/stats.js b/plugins/stats/stats.js index 65f30d53..3444a6f6 100644 --- a/plugins/stats/stats.js +++ b/plugins/stats/stats.js @@ -113,7 +113,7 @@ createStatElement(row, totalCount, 'Markers'); } - stash.addEventListener('page:stats', function() { + stash.addEventListener('stash:page:stats', function() { waitForElementByXpath("//div[contains(@class, 'container-fluid')]/div[@class='mt-5']", function(xpath, el) { if (!document.getElementById('custom-stats-row')) { const changelog = el.querySelector('div.changelog'); From fea02978ebc708a4c87e22cdc08b5638627204f0 Mon Sep 17 00:00:00 2001 From: raghavan Date: Fri, 19 Jan 2024 20:35:37 +0530 Subject: [PATCH 51/91] remove custom.js --- plugins/stashUserscriptLibrary/custom.js | 1434 ---------------------- 1 file changed, 1434 deletions(-) delete mode 100644 plugins/stashUserscriptLibrary/custom.js diff --git a/plugins/stashUserscriptLibrary/custom.js b/plugins/stashUserscriptLibrary/custom.js deleted file mode 100644 index f13164c5..00000000 --- a/plugins/stashUserscriptLibrary/custom.js +++ /dev/null @@ -1,1434 +0,0 @@ -const stashListener = new EventTarget(); - -const { - fetch: originalFetch -} = window; - -window.fetch = async (...args) => { - let [resource, config] = args; - // request interceptor here - const response = await originalFetch(resource, config); - // response interceptor here - const contentType = response.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1 && resource.endsWith('/graphql')) { - try { - const data = await response.clone().json(); - stashListener.dispatchEvent(new CustomEvent('response', { - 'detail': data - })); - } catch (e) { - - } - } - return response; -}; - -class Logger { - constructor(enabled) { - this.enabled = enabled; - } - debug() { - if (this.enabled) return; - console.debug(...arguments); - } -} - - -class Stash extends EventTarget { - constructor({ - pageUrlCheckInterval = 100, - detectReRenders = false, // detects if .main element is re-rendered. eg: When you are in scenes page and clicking the scenes nav tab the url wont change but the elements are re-rendered, So with this you can listen and alter the elements inside the .main node - logging = false - } = {}) { - super(); - this.log = new Logger(logging); - this._pageUrlCheckInterval = pageUrlCheckInterval; - this._detectReRenders = detectReRenders; - this.fireOnHashChangesToo = true; - this._lastPathStr = ""; - this._lastQueryStr = ""; - this._lastHashStr = ""; - this._lastHref = ""; - this._lastStashPageEvent = ""; - this.waitForElement(this._detectReRenders ? ".main > div" : "html").then(() => { - this._pageURLCheckTimerId = setInterval(() => { - // Loop every 100 ms - if ( - this._lastPathStr !== location.pathname || - this._lastQueryStr !== location.search || - (this.fireOnHashChangesToo && this._lastHashStr !== location.hash) || - this._lastHref !== location.href || - (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders) - ) { - this._dispatchPageEvent("stash:page", false) - this.gmMain({ - lastPathStr: this._lastPathStr, - lastQueryStr: this._lastQueryStr, - lastHashStr: this._lastHashStr, - lastHref: this._lastHref, - lastStashPageEvent: this._lastStashPageEvent, - }); - this._lastPathStr = location.pathname - this._lastQueryStr = location.search - this._lastHashStr = location.hash - this._lastHref = location.href - if (this._detectReRenders) { - this.waitForElement(".main > div", 10000).then((element) => { - element.setAttribute("stashUserscriptLibrary", ""); - }) - } - } - }, this._pageUrlCheckInterval); - }) - stashListener.addEventListener('response', (evt) => { - if (evt.detail.data?.plugins) { - this.getPluginVersion(evt.detail); - } - this.processRemoteScenes(evt.detail); - this.processScene(evt.detail); - this.processScenes(evt.detail); - this.processStudios(evt.detail); - this.processPerformers(evt.detail); - this.processApiKey(evt.detail); - this.dispatchEvent(new CustomEvent('stash:response', { - 'detail': evt.detail - })); - }); - stashListener.addEventListener('pluginVersion', (evt) => { - if (this.pluginVersion !== evt.detail) { - this.pluginVersion = evt.detail; - this.dispatchEvent(new CustomEvent('stash:pluginVersion', { - 'detail': evt.detail - })); - } - }); - this.version = [0, 0, 0]; - this.getVersion(); - this.pluginVersion = null; - this.getPlugins().then(plugins => this.getPluginVersion(plugins)); - this.visiblePluginTasks = ['Userscript Functions']; - this.settingsCallbacks = []; - this.settingsId = 'userscript-settings'; - this.remoteScenes = {}; - this.scenes = {}; - this.studios = {}; - this.performers = {}; - this.userscripts = []; - this._pageListeners = {}; - this.assignPageListeners() - } - async getVersion() { - const reqData = { - "operationName": "", - "variables": {}, - "query": `query version { - version { - version - } - }` - }; - const data = await this.callGQL(reqData); - const versionString = data.data.version.version; - this.version = versionString.substring(1).split('.').map(o => parseInt(o)); - } - compareVersion(minVersion) { - let [currMajor, currMinor, currPatch = 0] = this.version; - let [minMajor, minMinor, minPatch = 0] = minVersion.split('.').map(i => parseInt(i)); - if (currMajor > minMajor) return 1; - if (currMajor < minMajor) return -1; - if (currMinor > minMinor) return 1; - if (currMinor < minMinor) return -1; - return 0; - - } - comparePluginVersion(minPluginVersion) { - if (!this.pluginVersion) return -1; - let [currMajor, currMinor, currPatch = 0] = this.pluginVersion.split('.').map(i => parseInt(i)); - let [minMajor, minMinor, minPatch = 0] = minPluginVersion.split('.').map(i => parseInt(i)); - if (currMajor > minMajor) return 1; - if (currMajor < minMajor) return -1; - if (currMinor > minMinor) return 1; - if (currMinor < minMinor) return -1; - return 0; - - } - async runPluginTask(pluginId, taskName, args = []) { - const reqData = { - "operationName": "RunPluginTask", - "variables": { - "plugin_id": pluginId, - "task_name": taskName, - "args": args - }, - "query": "mutation RunPluginTask($plugin_id: ID!, $task_name: String!, $args: [PluginArgInput!]) {\n runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)\n}\n" - }; - return this.callGQL(reqData); - } - async callGQL(reqData) { - const options = { - method: 'POST', - body: JSON.stringify(reqData), - headers: { - 'Content-Type': 'application/json' - } - } - - try { - const res = await window.fetch('/graphql', options); - // this.log.debug(res); - return res.json(); - } catch (err) { - console.error(err); - } - } - async getFreeOnesStats(link) { - try { - const doc = await fetch(link) - .then(function(response) { - // When the page is loaded convert it to text - return response.text() - }) - .then(function(html) { - // Initialize the DOM parser - var parser = new DOMParser(); - - // Parse the text - var doc = parser.parseFromString(html, "text/html"); - - // You can now even select part of that html as you would in the regular DOM - // Example: - // var docArticle = doc.querySelector('article').innerHTML; - - console.log(doc); - return doc - }) - .catch(function(err) { - console.log('Failed to fetch page: ', err); - }); - - var data = new Object(); - data.rank = doc.querySelector('rank-chart-button'); - console.log(data.rank); - data.views = doc.querySelector('.d-none.d-m-flex.flex-column.align-items-center.global-header > div.font-weight-bold').textContent; - data.votes = '0' - return JSON.stringify(data); - } catch (err) { - console.error(err); - } - } - async getPlugins() { - const reqData = { - "operationName": "Plugins", - "variables": {}, - "query": `query Plugins { - plugins { - id - name - description - url - version - tasks { - name - description - __typename - } - hooks { - name - description - hooks - } - } - } - ` - }; - return this.callGQL(reqData); - } - async getPluginVersion(plugins) { - let version = null; - for (const plugin of plugins?.data?.plugins || []) { - if (plugin.id === 'userscript_functions') { - version = plugin.version; - } - } - stashListener.dispatchEvent(new CustomEvent('pluginVersion', { - 'detail': version - })); - } - async getStashBoxes() { - const reqData = { - "operationName": "Configuration", - "variables": {}, - "query": `query Configuration { - configuration { - general { - stashBoxes { - endpoint - api_key - name - } - } - } - }` - }; - return this.callGQL(reqData); - } - async getApiKey() { - const reqData = { - "operationName": "Configuration", - "variables": {}, - "query": `query Configuration { - configuration { - general { - apiKey - } - } - }` - }; - return this.callGQL(reqData); - } - matchUrl(href, fragment) { - const regexp = concatRegexp(new RegExp(window.location.origin), fragment); - // this.log.debug(regexp, location.href.match(regexp)); - return href.match(regexp) != null; - } - createSettings() { - waitForElementId('configuration-tabs-tabpane-system', async (elementId, el) => { - let section; - if (!document.getElementById(this.settingsId)) { - section = document.createElement("div"); - section.setAttribute('id', this.settingsId); - section.classList.add('setting-section'); - section.innerHTML = `

Userscript Settings

`; - el.appendChild(section); - - const expectedApiKey = (await this.getApiKey())?.data?.configuration?.general?.apiKey || ''; - const expectedUrl = window.location.origin; - - const serverUrlInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-url', 'userscript-server-url', 'Stash Server URL', '', 'Server URL…', true); - serverUrlInput.addEventListener('change', () => { - const value = serverUrlInput.value || ''; - if (value) { - this.updateConfigValueTask('STASH', 'url', value); - alert(`Userscripts plugin server URL set to ${value}`); - } else { - this.getConfigValueTask('STASH', 'url').then(value => { - serverUrlInput.value = value; - }); - } - }); - serverUrlInput.disabled = true; - serverUrlInput.value = expectedUrl; - this.getConfigValueTask('STASH', 'url').then(value => { - if (value !== expectedUrl) { - return this.updateConfigValueTask('STASH', 'url', expectedUrl); - } - }); - - const apiKeyInput = await this.createSystemSettingTextbox(section, 'userscript-section-server-apikey', 'userscript-server-apikey', 'Stash API Key', '', 'API Key…', true); - apiKeyInput.addEventListener('change', () => { - const value = apiKeyInput.value || ''; - this.updateConfigValueTask('STASH', 'api_key', value); - if (value) { - alert(`Userscripts plugin server api key set to ${value}`); - } else { - alert(`Userscripts plugin server api key value cleared`); - } - }); - apiKeyInput.disabled = true; - apiKeyInput.value = expectedApiKey; - this.getConfigValueTask('STASH', 'api_key').then(value => { - if (value !== expectedApiKey) { - return this.updateConfigValueTask('STASH', 'api_key', expectedApiKey); - } - }); - } else { - section = document.getElementById(this.settingsId); - } - - for (const callback of this.settingsCallbacks) { - callback(this.settingsId, section); - } - - if (this.pluginVersion) { - this.dispatchEvent(new CustomEvent('stash:pluginVersion', { - 'detail': this.pluginVersion - })); - } - - }); - } - addSystemSetting(callback) { - const section = document.getElementById(this.settingsId); - if (section) { - callback(this.settingsId, section); - } - this.settingsCallbacks.push(callback); - } - async createSystemSettingCheckbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader) { - const section = document.createElement("div"); - section.setAttribute('id', settingsId); - section.classList.add('card'); - section.style.display = 'none'; - section.innerHTML = `
-
-

${settingsHeader}

-
${settingsSubheader}
-
-
-
- - -
-
-
`; - containerEl.appendChild(section); - return document.getElementById(inputId); - } - async createSystemSettingTextbox(containerEl, settingsId, inputId, settingsHeader, settingsSubheader, placeholder, visible) { - const section = document.createElement("div"); - section.setAttribute('id', settingsId); - section.classList.add('card'); - section.style.display = visible ? 'flex' : 'none'; - section.innerHTML = `
-
-

${settingsHeader}

-
${settingsSubheader}
-
-
-
- -
-
-
`; - containerEl.appendChild(section); - return document.getElementById(inputId); - } - get serverUrl() { - return window.location.origin; - } - async waitForElement(selector, timeout = null, location = document.body, disconnectOnPageChange = false) { - return new Promise((resolve) => { - if (document.querySelector(selector)) { - return resolve(document.querySelector(selector)) - } - - const observer = new MutationObserver(async () => { - if (document.querySelector(selector)) { - resolve(document.querySelector(selector)) - observer.disconnect() - } else { - if (timeout) { - async function timeOver() { - return new Promise((resolve) => { - setTimeout(() => { - observer.disconnect() - resolve(false) - }, timeout) - }) - } - resolve(await timeOver()) - } - } - }) - - observer.observe(location, { - childList: true, - subtree: true, - }) - - const stash = this - if (disconnectOnPageChange) { - function disconnect() { - observer.disconnect() - stash.removeEventListener("stash:page", disconnect) - } - stash.addEventListener("stash:page", disconnect) - } - }) - } - async waitForElementDeath(selector, location = document.body, disconnectOnPageChange = false) { - return new Promise((resolve) => { - const observer = new MutationObserver(async () => { - if (!document.querySelector(selector)) { - resolve(true) - observer.disconnect() - } - }) - - observer.observe(location, { - childList: true, - subtree: true, - }) - - const stash = this - if (disconnectOnPageChange) { - function disconnect() { - observer.disconnect() - stash.removeEventListener("stash:page", disconnect) - } - stash.addEventListener("stash:page", disconnect) - } - }) - } - async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ - if (recursive) return - if (listenType === "tabs") { - const locationElement = await this.waitForElement(location, 10000, document.body, true) - const stash = this - let previousEvent = "" - function listenForTabClicks(domEvent) { - const clickedChild = domEvent.target ? domEvent.target : domEvent; - if(!clickedChild.classList?.contains("nav-link")) return - const tagName = clickedChild.getAttribute("data-rb-event-key") - const parentEvent = tagName.split("-")[0] - const childEvent = tagName.split("-").slice(1, -1).join("-") - event = `stash:page:${parentEvent}:${childEvent}` - if (previousEvent === event || !condition()) return - previousEvent = event - stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false) - stash._dispatchPageEvent(event) - } - if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) - locationElement.addEventListener("click", listenForTabClicks); - function removeEventListenerOnPageChange() { - locationElement.removeEventListener("click", listenForTabClicks) - stash.removeEventListener("stash:page", removeEventListenerOnPageChange) - } - stash.addEventListener("stash:page", removeEventListenerOnPageChange) - } else if (await this.waitForElement(selector, null, location, true)) { - this._dispatchPageEvent(event) - if (await this.waitForElementDeath(selector, location, true)) { - if (this._lastPathStr === window.location.pathname && !reRunGmMain) { - await this._listenForNonPageChanges({selector: selector, event: event}) - } else if (this._lastPathStr === window.location.pathname && reRunGmMain) { - this.gmMain({ - recursive: true, - lastPathStr: this._lastPathStr, - lastQueryStr: this._lastQueryStr, - lastHashStr: this._lastHashStr, - lastHref: this._lastHref, - lastStashPageEvent: this._lastStashPageEvent, - }); - } - } - } - callback() - } - _dispatchPageEvent(event, addToHistory = true) { - this.dispatchEvent(new CustomEvent(event, { - detail: { - event: event, - lastEventState: { - lastPathStr: this._lastPathStr, - lastQueryStr: this._lastQueryStr, - lastHashStr: this._lastHashStr, - lastHref: this._lastHref, - lastStashPageEvent: this._lastStashPageEvent, - } - } - })) - if (addToHistory) { - this.log.debug(`[Navigation] ${event}`); - if (event.startsWith("stash:")) { - this._lastStashPageEvent = event; - } - } - } - addPageListener(eventData) { - const {event, regex, callBack = () => {}, manuallyHandleDispatchEvent = false} = eventData - if (event && !event?.startsWith("stash:") && regex && this._pageListeners[event] === undefined){ - this._pageListeners[event] = { - regex: regex, - callBack: callBack, - manuallyHandleDispatchEvent: manuallyHandleDispatchEvent - } - return event - } else { - if (this._pageListeners[event] !== undefined) { - console.error(`Can't add page listener: Event ${event} already exists`) - } else if (event?.startsWith("stash:")) { - console.error(`Can't add page listener: Event name can't start with "stash:"`) - } else { - console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`) - } - return false - } - } - removePageListener(event) { - if(event && !event?.startsWith("stash:") && this._pageListeners[event]){ - delete this._pageListeners[event] - return event - } else { - if (this._pageListeners[event] === undefined && event) { - console.error(`Can't remove page listener: Event ${event} doesn't exists`) - } else if (event?.startsWith("stash:")) { - console.error(`Can't remove page listener: Event ${event} is a built in event`) - } else { - console.error(`Can't remove page listener: Missing "event" argument`) - } - return false - } - } - stopPageListener() { - clearInterval(this._pageURLCheckTimerId) - } - assignPageListeners() { - this._pageListeners = { - // scenes tab - "stash:page:scenes": { - regex: /\/scenes\?/, - handleDisplayView: true, - callBack: () => this.processTagger() - }, - "stash:page:scene:new": { - regex: /\/scenes\/new/ - }, - "stash:page:scene": { - regex: /\/scenes\/\d+/, - callBack: ({recursive = false}) => this._listenForNonPageChanges({ - location: ".scene-tabs .nav-tabs", - listenType: "tabs", - recursive: recursive - }) - }, - - // images tab - "stash:page:images": { - regex: /\/images\?/, - handleDisplayView: true, - }, - "stash:page:image": { - regex: /\/images\/\d+/, - callBack: ({recursive = false}) => this._listenForNonPageChanges({ - location: ".image-tabs .nav-tabs", - listenType: "tabs", - recursive: recursive - }) - }, - - // movies tab - "stash:page:movies": { - regex: /\/movies\?/, - }, - "stash:page:movie": { - regex: /\/movies\/\d+/, - }, - "stash:page:movie:scenes": { - regex: /\/movies\/\d+\?/, - callBack: () => this.processTagger() - }, - - // markers tab - "stash:page:markers": { - regex: /\/scenes\/markers/ - }, - - // galleries tab - "stash:page:galleries": { - regex: /\/galleries\?/, - handleDisplayView: true, - }, - "stash:page:gallery:new": { - regex: /\/galleries\/new/, - }, - "stash:page:gallery:images": { - regex: /\/galleries\/\d+\?/, - manuallyHandleDispatchEvent: true, - handleDisplayView: "ignoreDisplayViewCondition", - callBack: ({lastHref, recursive = false}, event) => { - if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ - this._dispatchPageEvent("stash:page:gallery"); - this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) - } - - this._dispatchPageEvent(event); - - this._listenForNonPageChanges({ - location: ".gallery-tabs .nav-tabs", - listenType: "tabs", - recursive: recursive, - listenDefaultTab: false - }) - } - }, - "stash:page:gallery:add": { - regex: /\/galleries\/\d+\/add/, - manuallyHandleDispatchEvent: true, - handleDisplayView: "ignoreDisplayViewCondition", - callBack: ({lastHref, recursive = false}, event) => { - if(!this.matchUrl(lastHref, /\/galleries\/\d+/)){ - this._dispatchPageEvent("stash:page:gallery"); - this._listenForNonPageChanges({selector: ".gallery-tabs .nav-tabs .nav-link.active", event: "stash:page:gallery:details"}) - } - - this._dispatchPageEvent(event); - - this._listenForNonPageChanges({ - location: ".gallery-tabs .nav-tabs", - listenType: "tabs", - recursive: recursive, - listenDefaultTab: false - }) - } - }, - - // performers tab - "stash:page:performers": { - regex: /\/performers\?/, - manuallyHandleDispatchEvent: true, - handleDisplayView: true, - callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/performers\?/) || this._detectReRenders ? this._dispatchPageEvent(event) : null - }, - "stash:page:performer:new": { - regex: /\/performers\/new/ - }, - "stash:page:performer": { - regex: /\/performers\/\d+/, - manuallyHandleDispatchEvent: true, - callBack: ({lastHref}, event) => { - if(!this.matchUrl(lastHref, /\/performers\/\d+/)){ - this._dispatchPageEvent(event); - this.processTagger(); - } - - this._listenForNonPageChanges({ - selector: "#performer-edit", - event: "stash:page:performer:edit", - reRunGmMain: true, - callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null - }) - } - }, - "stash:page:performer:scenes": { - regex: /\/performers\/\d+\?/, - handleDisplayView: true, - }, - "stash:page:performer:galleries": { - regex: /\/performers\/\d+\/galleries/, - handleDisplayView: true - }, - "stash:page:performer:images": { - regex: /\/performers\/\d+\/images/, - handleDisplayView: true - }, - "stash:page:performer:movies": { - regex: /\/performers\/\d+\/movies/ - }, - "stash:page:performer:appearswith": { - regex: /\/performers\/\d+\/appearswith/, - handleDisplayView: true, - callBack: () => this.processTagger() - }, - - // studios tab - "stash:page:studios": { - regex: /\/studios\?/, - handleDisplayView: true, - }, - "stash:page:studio:new": { - regex: /\/studios\/new/ - }, - "stash:page:studio": { - regex: /\/studios\/\d+/, - manuallyHandleDispatchEvent: true, - callBack: ({lastHref}, event) => { - if(!this.matchUrl(lastHref, /\/studios\/\d+/)){ - this._dispatchPageEvent(event); - this.processTagger(); - } - - this._listenForNonPageChanges({ - selector: "#studio-edit", - event: "stash:page:studio:edit", - reRunGmMain: true, - callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null - }) - } - }, - "stash:page:studio:scenes": { - regex: /\/studios\/\d+\?/, - handleDisplayView: true, - }, - "stash:page:studio:galleries": { - regex: /\/studios\/\d+\/galleries/, - handleDisplayView: true, - }, - "stash:page:studio:images": { - regex: /\/studios\/\d+\/images/, - handleDisplayView: true, - }, - "stash:page:studio:performers": { - regex: /\/studios\/\d+\/performers/, - handleDisplayView: true, - }, - "stash:page:studio:movies": { - regex: /\/studios\/\d+\/movies/ - }, - "stash:page:studio:childstudios": { - regex: /\/studios\/\d+\/childstudios/, - handleDisplayView: true, - }, - - // tags tab - "stash:page:tags": { - regex: /\/tags\?/, - handleDisplayView: true, - }, - "stash:page:tag:new": { - regex: /\/tags\/new/ - }, - "stash:page:tag": { - regex: /\/tags\/\d+/, - manuallyHandleDispatchEvent: true, - callBack: ({lastHref}, event) => { - if(!this.matchUrl(lastHref, /\/tags\/\d+/)){ - this._dispatchPageEvent(event); - this.processTagger(); - } - - this._listenForNonPageChanges({ - selector: "#tag-edit", - event: "stash:page:tag:edit", - reRunGmMain: true, - callback: () => this._detectReRenders ? this._dispatchPageEvent(event) : null - }) - } - }, - "stash:page:tag:scenes": { - regex: /\/tags\/\d+\?/, - handleDisplayView: true, - }, - "stash:page:tag:galleries": { - regex: /\/tags\/\d+\/galleries/, - handleDisplayView: true, - }, - "stash:page:tag:images": { - regex: /\/tags\/\d+\/images/, - handleDisplayView: true, - }, - "stash:page:tag:markers": { - regex: /\/tags\/\d+\/markers/ - }, - "stash:page:tag:performers": { - regex: /\/tags\/\d+\/performers/, - handleDisplayView: true, - }, - - // settings page - "stash:page:settings": { - regex: /\/settings/, - manuallyHandleDispatchEvent: true, - callBack: ({lastHref}, event) => !this.matchUrl(lastHref, /\/settings/) ? this._dispatchPageEvent(event) : null - }, - "stash:page:settings:tasks": { - regex: /\/settings\?tab=tasks/, - callback: () => this.hidePluginTasks() - }, - "stash:page:settings:library": { - regex: /\/settings\?tab=library/ - }, - "stash:page:settings:interface": { - regex: /\/settings\?tab=interface/ - }, - "stash:page:settings:security": { - regex: /\/settings\?tab=security/ - }, - "stash:page:settings:metadata-providers": { - regex: /\/settings\?tab=metadata-providers/ - }, - "stash:page:settings:services": { - regex: /\/settings\?tab=services/ - }, - "stash:page:settings:system": { - regex: /\/settings\?tab=system/, - callBack: () => this.createSettings() - }, - "stash:page:settings:plugins": { - regex: /\/settings\?tab=plugins/ - }, - "stash:page:settings:logs": { - regex: /\/settings\?tab=logs/ - }, - "stash:page:settings:tools": { - regex: /\/settings\?tab=tools/ - }, - "stash:page:settings:changelog": { - regex: /\/settings\?tab=changelog/ - }, - "stash:page:settings:about": { - regex: /\/settings\?tab=about/ - }, - - // stats page - "stash:page:stats": { - regex: /\/stats/ - }, - - // home page - "stash:page:home": { - regex: /\/$/, - callBack: () => this._listenForNonPageChanges({selector: ".recommendations-container-edit", event: "stash:page:home:edit", reRunGmMain: true}) - }, - } - } - gmMain(args) { - const events = Object.keys(this._pageListeners) - - for (const event of events) { - const {regex, callBack = async () => {}, manuallyHandleDispatchEvent = false, handleDisplayView = false} = this._pageListeners[event] - - let isDisplayViewPage = false - let isListPage, isWallPage, isTaggerPage - - if (handleDisplayView) { - isListPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=1/)) - isWallPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=2/)) - isTaggerPage = this.matchUrl(window.location.href, concatRegexp(regex, /.*disp=3/)) - - if (isListPage || isWallPage || isTaggerPage) isDisplayViewPage = true - } - - const handleDisplayViewCondition = handleDisplayView !== true || (handleDisplayView && (!isDisplayViewPage || args.lastHref === "")) - - if (this.matchUrl(window.location.href, regex) && handleDisplayViewCondition) { - if (!manuallyHandleDispatchEvent) this._dispatchPageEvent(event) - callBack({...args, location: window.location}, event) - } - - if (handleDisplayView) { - if (isListPage) { - this._dispatchPageEvent("stash:page:any:list", false); - this._dispatchPageEvent(event + ":list"); - } else if (isWallPage) { - this._dispatchPageEvent("stash:page:any:wall", false); - this._dispatchPageEvent(event + ":wall"); - } else if (isTaggerPage) { - this._dispatchPageEvent("stash:page:any:tagger", false); - this._dispatchPageEvent(event + ":tagger"); - } - } - } - } - addEventListeners(events, callback, ...options) { - events.forEach((event) => { - this.addEventListener(event, callback, ...options); - }); - } - hidePluginTasks() { - // hide userscript functions plugin tasks - waitForElementByXpath("//div[@id='tasks-panel']//h3[text()='Userscript Functions']/ancestor::div[contains(@class, 'setting-group')]", (elementId, el) => { - const tasks = el.querySelectorAll('.setting'); - for (const task of tasks) { - const taskName = task.querySelector('h3').innerText; - task.classList.add(this.visiblePluginTasks.indexOf(taskName) === -1 ? 'd-none' : 'd-flex'); - this.dispatchEvent(new CustomEvent('stash:plugin:task', { - 'detail': { - taskName, - task - } - })); - } - }); - } - async updateConfigValueTask(sectionKey, propName, value) { - return this.runPluginTask("userscript_functions", "Update Config Value", [{ - "key": "section_key", - "value": { - "str": sectionKey - } - }, { - "key": "prop_name", - "value": { - "str": propName - } - }, { - "key": "value", - "value": { - "str": value - } - }]); - } - async getConfigValueTask(sectionKey, propName) { - await this.runPluginTask("userscript_functions", "Get Config Value", [{ - "key": "section_key", - "value": { - "str": sectionKey - } - }, { - "key": "prop_name", - "value": { - "str": propName - } - }]); - - // poll logs until plugin task output appears - const prefix = `[Plugin / Userscript Functions] get_config_value: [${sectionKey}][${propName}] =`; - return this.pollLogsForMessage(prefix); - } - async pollLogsForMessage(prefix) { - const reqTime = Date.now(); - const reqData = { - "variables": {}, - "query": `query Logs { - logs { - time - level - message - } - }` - }; - await new Promise(r => setTimeout(r, 500)); - let retries = 0; - while (true) { - const delay = 2 ** retries * 100; - await new Promise(r => setTimeout(r, delay)); - retries++; - - const logs = await this.callGQL(reqData); - for (const log of logs.data.logs) { - const logTime = Date.parse(log.time); - if (logTime > reqTime && log.message.startsWith(prefix)) { - return log.message.replace(prefix, '').trim(); - } - } - - if (retries >= 5) { - throw `Poll logs failed for message: ${prefix}`; - } - } - } - processTagger() { - waitForElementByXpath("//button[text()='Scrape All']", (xpath, el) => { - this.dispatchEvent(new CustomEvent('tagger', { - 'detail': el - })); - - const searchItemContainer = document.querySelector('.tagger-container').lastChild; - - const observer = new MutationObserver(mutations => { - mutations.forEach(mutation => { - mutation.addedNodes.forEach(node => { - if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Performer:')) { - this.dispatchEvent(new CustomEvent('tagger:mutation:add:remoteperformer', { - 'detail': { - node, - mutation - } - })); - } else if (node?.classList?.contains('entity-name') && node.innerText.startsWith('Studio:')) { - this.dispatchEvent(new CustomEvent('tagger:mutation:add:remotestudio', { - 'detail': { - node, - mutation - } - })); - } else if (node.tagName === 'SPAN' && node.innerText.startsWith('Matched:')) { - this.dispatchEvent(new CustomEvent('tagger:mutation:add:local', { - 'detail': { - node, - mutation - } - })); - } else if (node.tagName === 'UL') { - this.dispatchEvent(new CustomEvent('tagger:mutation:add:container', { - 'detail': { - node, - mutation - } - })); - } else if (node?.classList?.contains('col-lg-6')) { - this.dispatchEvent(new CustomEvent('tagger:mutation:add:subcontainer', { - 'detail': { - node, - mutation - } - })); - } else if (node.tagName === 'H5') { // scene date - this.dispatchEvent(new CustomEvent('tagger:mutation:add:date', { - 'detail': { - node, - mutation - } - })); - } else if (node.tagName === 'DIV' && node?.classList?.contains('d-flex') && node?.classList?.contains('flex-column')) { // scene stashid, url, details - this.dispatchEvent(new CustomEvent('tagger:mutation:add:detailscontainer', { - 'detail': { - node, - mutation - } - })); - } else { - this.dispatchEvent(new CustomEvent('tagger:mutation:add:other', { - 'detail': { - node, - mutation - } - })); - } - }); - }); - this.dispatchEvent(new CustomEvent('tagger:mutations:searchitems', { - 'detail': mutations - })); - }); - observer.observe(searchItemContainer, { - childList: true, - subtree: true - }); - - const taggerContainerHeader = document.querySelector('.tagger-container-header'); - const taggerContainerHeaderObserver = new MutationObserver(mutations => { - this.dispatchEvent(new CustomEvent('tagger:mutations:header', { - 'detail': mutations - })); - }); - taggerContainerHeaderObserver.observe(taggerContainerHeader, { - childList: true, - subtree: true - }); - - for (const searchItem of document.querySelectorAll('.search-item')) { - this.dispatchEvent(new CustomEvent('tagger:searchitem', { - 'detail': searchItem - })); - } - - if (!document.getElementById('progress-bar')) { - const progressBar = createElementFromHTML(`
`); - progressBar.classList.add('progress'); - progressBar.style.display = 'none'; - taggerContainerHeader.appendChild(progressBar); - } - }); - waitForElementByXpath("//div[@class='tagger-container-header']/div/div[@class='row']/h4[text()='Configuration']", (xpath, el) => { - this.dispatchEvent(new CustomEvent('tagger:configuration', { - 'detail': el - })); - }); - } - setProgress(value) { - const progressBar = document.getElementById('progress-bar'); - if (progressBar) { - progressBar.firstChild.style.width = value + '%'; - progressBar.style.display = (value <= 0 || value > 100) ? 'none' : 'flex'; - } - } - processRemoteScenes(data) { - if (data.data?.scrapeMultiScenes) { - for (const matchResults of data.data.scrapeMultiScenes) { - for (const scene of matchResults) { - this.remoteScenes[scene.remote_site_id] = scene; - } - } - } else if (data.data?.scrapeSingleScene) { - for (const scene of data.data.scrapeSingleScene) { - this.remoteScenes[scene.remote_site_id] = scene; - } - } - } - processScene(data) { - if (data.data.findScene) { - this.scenes[data.data.findScene.id] = data.data.findScene; - } - } - processScenes(data) { - if (data.data.findScenes?.scenes) { - for (const scene of data.data.findScenes.scenes) { - this.scenes[scene.id] = scene; - } - } - } - processStudios(data) { - if (data.data.findStudios?.studios) { - for (const studio of data.data.findStudios.studios) { - this.studios[studio.id] = studio; - } - } - } - processPerformers(data) { - if (data.data.findPerformers?.performers) { - for (const performer of data.data.findPerformers.performers) { - this.performers[performer.id] = performer; - } - } - } - processApiKey(data) { - if (data.data.generateAPIKey != null && this.pluginVersion) { - this.updateConfigValueTask('STASH', 'api_key', data.data.generateAPIKey); - } - } - parseSearchItem(searchItem) { - const urlNode = searchItem.querySelector('a.scene-link'); - const url = new URL(urlNode.href); - const id = url.pathname.replace('/scenes/', ''); - const data = this.scenes[id]; - const nameNode = searchItem.querySelector('a.scene-link > div.TruncatedText'); - const name = nameNode.innerText; - const queryInput = searchItem.querySelector('input.text-input'); - const performerNodes = searchItem.querySelectorAll('.performer-tag-container'); - - return { - urlNode, - url, - id, - data, - nameNode, - name, - queryInput, - performerNodes - } - } - parseSearchResultItem(searchResultItem) { - const remoteUrlNode = searchResultItem.querySelector('.scene-details .optional-field .optional-field-content a'); - const remoteId = remoteUrlNode?.href.split('/').pop(); - const remoteUrl = remoteUrlNode?.href ? new URL(remoteUrlNode.href) : null; - const remoteData = this.remoteScenes[remoteId]; - - const sceneDetailNodes = searchResultItem.querySelectorAll('.scene-details .optional-field .optional-field-content'); - let urlNode = null; - let detailsNode = null; - for (const sceneDetailNode of sceneDetailNodes) { - if (sceneDetailNode.innerText.startsWith('http') && (remoteUrlNode?.href !== sceneDetailNode.innerText)) { - urlNode = sceneDetailNode; - } else if (!sceneDetailNode.innerText.startsWith('http')) { - detailsNode = sceneDetailNode; - } - } - - const imageNode = searchResultItem.querySelector('.scene-image-container .optional-field .optional-field-content'); - - const metadataNode = searchResultItem.querySelector('.scene-metadata'); - const titleNode = metadataNode.querySelector('h4 .optional-field .optional-field-content'); - const codeAndDateNodes = metadataNode.querySelectorAll('h5 .optional-field .optional-field-content'); - let codeNode = null; - let dateNode = null; - for (const node of codeAndDateNodes) { - if (node.textContent.includes('-')) { - dateNode = node; - } else { - codeNode = node; - } - } - - const entityNodes = searchResultItem.querySelectorAll('.entity-name'); - let studioNode = null; - const performerNodes = []; - for (const entityNode of entityNodes) { - if (entityNode.innerText.startsWith('Studio:')) { - studioNode = entityNode; - } else if (entityNode.innerText.startsWith('Performer:')) { - performerNodes.push(entityNode); - } - } - - const matchNodes = searchResultItem.querySelectorAll('div.col-lg-6 div.mt-2 div.row.no-gutters.my-2 span.ml-auto'); - const matches = [] - for (const matchNode of matchNodes) { - let matchType = null; - const entityNode = matchNode.parentElement.querySelector('.entity-name'); - - const matchName = matchNode.querySelector('.optional-field-content b').innerText; - const remoteName = entityNode.querySelector('b').innerText; - - let data; - if (entityNode.innerText.startsWith('Performer:')) { - matchType = 'performer'; - if (remoteData) { - data = remoteData.performers.find(performer => performer.name === remoteName); - } - } else if (entityNode.innerText.startsWith('Studio:')) { - matchType = 'studio'; - if (remoteData) { - data = remoteData.studio - } - } - - matches.push({ - matchType, - matchNode, - entityNode, - matchName, - remoteName, - data - }); - } - - return { - remoteUrlNode, - remoteId, - remoteUrl, - remoteData, - urlNode, - detailsNode, - imageNode, - titleNode, - codeNode, - dateNode, - studioNode, - performerNodes, - matches - } - } -} - -window.stash = new Stash(); - -function waitForElementQuerySelector(query, callBack, time) { - time = (typeof time !== 'undefined') ? time : 100; - window.setTimeout(() => { - const element = document.querySelector(query); - if (element) { - callBack(query, element); - } else { - waitForElementQuerySelector(query, callBack, time); - } - }, time); -} - -function waitForElementClass(elementId, callBack, time) { - time = (typeof time !== 'undefined') ? time : 100; - window.setTimeout(() => { - const element = document.getElementsByClassName(elementId); - if (element.length > 0) { - callBack(elementId, element); - } else { - waitForElementClass(elementId, callBack, time); - } - }, time); -} - -function waitForElementId(elementId, callBack, time) { - time = (typeof time !== 'undefined') ? time : 100; - window.setTimeout(() => { - const element = document.getElementById(elementId); - if (element != null) { - callBack(elementId, element); - } else { - waitForElementId(elementId, callBack, time); - } - }, time); -} - -function waitForElementByXpath(xpath, callBack, time) { - time = (typeof time !== 'undefined') ? time : 100; - window.setTimeout(() => { - const element = getElementByXpath(xpath); - if (element) { - callBack(xpath, element); - } else { - waitForElementByXpath(xpath, callBack, time); - } - }, time); -} - -function getElementByXpath(xpath, contextNode) { - return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; -} - -function createElementFromHTML(htmlString) { - const div = document.createElement('div'); - div.innerHTML = htmlString.trim(); - - // Change this to div.childNodes to support multiple top-level nodes. - return div.firstChild; -} - -function getElementByXpath(xpath, contextNode) { - return document.evaluate(xpath, contextNode || document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; -} - -function getElementsByXpath(xpath, contextNode) { - return document.evaluate(xpath, contextNode || document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); -} - -function getClosestAncestor(el, selector, stopSelector) { - let retval = null; - while (el) { - if (el.matches(selector)) { - retval = el; - break - } else if (stopSelector && el.matches(stopSelector)) { - break - } - el = el.parentElement; - } - return retval; -} - -function setNativeValue(element, value) { - const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set; - const prototype = Object.getPrototypeOf(element); - const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set; - - if (valueSetter && valueSetter !== prototypeValueSetter) { - prototypeValueSetter.call(element, value); - } else { - valueSetter.call(element, value); - } -} - -function updateTextInput(element, value) { - setNativeValue(element, value); - element.dispatchEvent(new Event('input', { - bubbles: true - })); -} - -function concatRegexp(reg, exp) { - let flags = reg.flags + exp.flags; - flags = Array.from(new Set(flags.split(''))).join(); - return new RegExp(reg.source + exp.source, flags); -} - -function sortElementChildren(node) { - const items = node.childNodes; - const itemsArr = []; - for (const i in items) { - if (items[i].nodeType == Node.ELEMENT_NODE) { // get rid of the whitespace text nodes - itemsArr.push(items[i]); - } - } - - itemsArr.sort((a, b) => { - return a.innerHTML == b.innerHTML ? - 0 : - (a.innerHTML > b.innerHTML ? 1 : -1); - }); - - for (let i = 0; i < itemsArr.length; i++) { - node.appendChild(itemsArr[i]); - } -} - -function xPathResultToArray(result) { - let node = null; - const nodes = []; - while (node = result.iterateNext()) { - nodes.push(node); - } - return nodes; -} - -function createStatElement(container, title, heading) { - const statEl = document.createElement('div'); - statEl.classList.add('stats-element'); - container.appendChild(statEl); - - const statTitle = document.createElement('p'); - statTitle.classList.add('title'); - statTitle.innerText = title; - statEl.appendChild(statTitle); - - const statHeading = document.createElement('p'); - statHeading.classList.add('heading'); - statHeading.innerText = heading; - statEl.appendChild(statHeading); -} - -const reloadImg = url => - fetch(url, { - cache: 'reload', - mode: 'no-cors' - }) - .then(() => document.body.querySelectorAll(`img[src='${url}']`) - .forEach(img => img.src = url)); From 9a8124c06c976b6f59748496df996eeb06069292 Mon Sep 17 00:00:00 2001 From: raghavan Date: Fri, 19 Jan 2024 21:35:22 +0530 Subject: [PATCH 52/91] fix formatting --- plugins/stashUserscriptLibrary/stashUserscriptLibrary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index 79152106..cdf070ff 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -517,7 +517,7 @@ class Stash extends EventTarget { _dispatchPageEvent(event, addToHistory = true) { this.dispatchEvent(new CustomEvent(event, { detail: { -event: event, + event: event, lastEventState: { lastPathStr: this._lastPathStr, lastQueryStr: this._lastQueryStr, From f60e0a5194c6a39fb3e6792d448760eb4d443062 Mon Sep 17 00:00:00 2001 From: raghavan Date: Fri, 19 Jan 2024 21:38:09 +0530 Subject: [PATCH 53/91] better naming --- plugins/stashUserscriptLibrary/stashUserscriptLibrary.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index cdf070ff..aa519a31 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -115,7 +115,7 @@ class Stash extends EventTarget { this.performers = {}; this.userscripts = []; this._pageListeners = {}; - this.assignPageListeners() + this._initDefaultPageListeners() } async getVersion() { const reqData = { @@ -572,7 +572,7 @@ class Stash extends EventTarget { stopPageListener() { clearInterval(this._pageURLCheckTimerId) } - assignPageListeners() { + _initDefaultPageListeners() { this._pageListeners = { // scenes tab "stash:page:scenes": { From 389c263e228b7bdabfa1b05a4e5af17f89aaa4c2 Mon Sep 17 00:00:00 2001 From: raghavan Date: Fri, 19 Jan 2024 22:20:55 +0530 Subject: [PATCH 54/91] readable code --- .../stashUserscriptLibrary.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js index aa519a31..0e5f6777 100644 --- a/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js +++ b/plugins/stashUserscriptLibrary/stashUserscriptLibrary.js @@ -61,6 +61,7 @@ class Stash extends EventTarget { (!document.querySelector(".main > div[stashUserscriptLibrary]") && this._detectReRenders) ) { this._dispatchPageEvent("stash:page", false) + this.gmMain({ lastPathStr: this._lastPathStr, lastQueryStr: this._lastQueryStr, @@ -68,10 +69,12 @@ class Stash extends EventTarget { lastHref: this._lastHref, lastStashPageEvent: this._lastStashPageEvent, }); + this._lastPathStr = location.pathname this._lastQueryStr = location.search this._lastHashStr = location.hash this._lastHref = location.href + if (this._detectReRenders) { this.waitForElement(".main > div", 10000).then((element) => { element.setAttribute("stashUserscriptLibrary", ""); @@ -472,24 +475,34 @@ class Stash extends EventTarget { } async _listenForNonPageChanges({selector = "", location = document.body, listenType = "", event = "", recursive = false, reRunGmMain = false, condition = () => true, listenDefaultTab = true, callback = () => {}} = {}){ if (recursive) return + if (listenType === "tabs") { const locationElement = await this.waitForElement(location, 10000, document.body, true) const stash = this let previousEvent = "" + function listenForTabClicks(domEvent) { const clickedChild = domEvent.target ? domEvent.target : domEvent; + if(!clickedChild.classList?.contains("nav-link")) return + const tagName = clickedChild.getAttribute("data-rb-event-key") const parentEvent = tagName.split("-")[0] const childEvent = tagName.split("-").slice(1, -1).join("-") + event = `stash:page:${parentEvent}:${childEvent}` + if (previousEvent === event || !condition()) return previousEvent = event + stash._dispatchPageEvent(`stash:page:any:${childEvent}`, false) stash._dispatchPageEvent(event) } + if (listenDefaultTab) listenForTabClicks(locationElement.querySelector(".nav-link.active")) + locationElement.addEventListener("click", listenForTabClicks); + function removeEventListenerOnPageChange() { locationElement.removeEventListener("click", listenForTabClicks) stash.removeEventListener("stash:page", removeEventListenerOnPageChange) @@ -497,6 +510,7 @@ class Stash extends EventTarget { stash.addEventListener("stash:page", removeEventListenerOnPageChange) } else if (await this.waitForElement(selector, null, location, true)) { this._dispatchPageEvent(event) + if (await this.waitForElementDeath(selector, location, true)) { if (this._lastPathStr === window.location.pathname && !reRunGmMain) { await this._listenForNonPageChanges({selector: selector, event: event}) @@ -542,6 +556,7 @@ class Stash extends EventTarget { callBack: callBack, manuallyHandleDispatchEvent: manuallyHandleDispatchEvent } + return event } else { if (this._pageListeners[event] !== undefined) { @@ -551,6 +566,7 @@ class Stash extends EventTarget { } else { console.error(`Can't add page listener: Missing required argument(s) "event", "regex"`) } + return false } } @@ -566,6 +582,7 @@ class Stash extends EventTarget { } else { console.error(`Can't remove page listener: Missing "event" argument`) } + return false } } From 3690daa72fcb4360e519eaf9f3f5b813875695da Mon Sep 17 00:00:00 2001 From: Maista6969 Date: Sat, 20 Jan 2024 01:00:37 +0100 Subject: [PATCH 55/91] Update kodi-helper Replaces fields that were deprecated in v0.24 --- scripts/kodi-helper/kodi-helper.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/kodi-helper/kodi-helper.py b/scripts/kodi-helper/kodi-helper.py index 2e79f4d5..d46e4c95 100644 --- a/scripts/kodi-helper/kodi-helper.py +++ b/scripts/kodi-helper/kodi-helper.py @@ -35,7 +35,7 @@ def main(): def generateNFOFiles(args): - if not args.inline and args.outdir == "": + if not (args.inline or args.outdir): print("--outdir or --inline must be set\n") return @@ -54,6 +54,12 @@ def generateNFOFiles(args): scenes = getScenes(i, filter) for scene in scenes: + # skip scenes without files + if not scene["files"]: + continue + + # Quick fix for scenes with multiple files + scene["path"] = scene["files"][0]["path"] # don't regenerate if file already exists and not overwriting output = getOutputNFOFile(scene["path"], args) if not args.overwrite and os.path.exists(output): @@ -165,11 +171,12 @@ def getScenes(page, sceneFilter): scenes { id title - path - rating + files { + path + } + rating100 details date - oshash paths { screenshot stream @@ -244,8 +251,9 @@ def generateNFO(scene, args): {}""".format(t["name"]) rating = "" - if scene["rating"] != None: - rating = scene["rating"] + if scene["rating100"] != None: + # Kodi uses a 10 point scale, in increments of 0.5 + rating = (2 * scene["rating100"] // 10) / 2.0 date = "" if scene["date"] != None: From 7ff8a11330322f885bf82f88f4a2558417df57b3 Mon Sep 17 00:00:00 2001 From: cc1234 Date: Sat, 20 Jan 2024 11:27:47 +0000 Subject: [PATCH 56/91] Add initial version of visage as a plugin --- plugins/visage/visage.css | 1 + plugins/visage/visage.js | 11811 ++++++++++++++++++++++++++++++++++++ plugins/visage/visage.yml | 13 + 3 files changed, 11825 insertions(+) create mode 100644 plugins/visage/visage.css create mode 100644 plugins/visage/visage.js create mode 100644 plugins/visage/visage.yml diff --git a/plugins/visage/visage.css b/plugins/visage/visage.css new file mode 100644 index 00000000..7b701530 --- /dev/null +++ b/plugins/visage/visage.css @@ -0,0 +1 @@ +button.svelte-1m5gxnd{background-color:var(--nav-color);border:0px}.scanner.svelte-1m5gxnd{animation:svelte-1m5gxnd-pulse 2s infinite}@keyframes svelte-1m5gxnd-pulse{0%{transform:scale(0.95);box-shadow:0 0 0 0 var(--light)}70%{transform:scale(1.1);box-shadow:0 0 0 10px var(--info)}100%{transform:scale(0.95);box-shadow:0 0 0 0 var(--primary)}}svg.svelte-1m5gxnd{fill:#ffffff}button.svelte-1m5gxnd{background-color:var(--nav-color);border:0px}.scanner.svelte-1m5gxnd{animation:svelte-1m5gxnd-pulse 2s infinite}@keyframes svelte-1m5gxnd-pulse{0%{transform:scale(0.95);box-shadow:0 0 0 0 var(--light)}70%{transform:scale(1.1);box-shadow:0 0 0 10px var(--info)}100%{transform:scale(0.95);box-shadow:0 0 0 0 var(--primary)}}svg.svelte-1m5gxnd{fill:#ffffff}.carousel.svelte-ssoxzi{display:flex;overflow-x:auto;overflow-y:auto;white-space:nowrap;overscroll-behavior-x:contain;overscroll-behavior-y:contain;scroll-snap-type:x mandatory;gap:1rem}.modal-header.svelte-ssoxzi{font-size:2.4rem;border-bottom:0px;padding:10px 10px 0px 10px}.modal-footer.svelte-ssoxzi{border-top:0px}.svelte-ssoxzi::-webkit-scrollbar{width:30px}.svelte-ssoxzi::-webkit-scrollbar-thumb{background:var(--orange);border-radius:20px}.card.svelte-ssoxzi{min-width:250px}.performer-card.svelte-ssoxzi{cursor:pointer}.assigned.svelte-ssoxzi{border:5px solid var(--green);animation:border 1s ease-in-out}.face-tab.svelte-ssoxzi{width:50px;height:50px;object-fit:cover}.selected.svelte-p95y28{border:2px solid #007bff}.face-tabs.svelte-p95y28{position:absolute;flex:0 0 450px;max-width:450px;min-width:450px;height:100%;overflow:auto;order:-1;background-color:var(--body-color)}.face-item.svelte-p95y28{width:160px;height:90px;border-radius:5px 5px 0px 0px;position:relative;cursor:pointer}.svelte-tabs__tab.svelte-1fbofsd{border:none;border-bottom:2px solid transparent;color:#000000;cursor:pointer;list-style:none;display:inline-block;padding:0.5em 0.75em}.svelte-tabs__tab.svelte-1fbofsd:focus{outline:thin dotted}.svelte-tabs__selected.svelte-1fbofsd{border-bottom:2px solid #4F81E5;color:#4F81E5}.svelte-tabs__tab-list.svelte-12yby2a{border-bottom:1px solid #CCCCCC;margin:0;padding:0}.svelte-tabs__tab-panel.svelte-epfyet{margin-top:0.5em} \ No newline at end of file diff --git a/plugins/visage/visage.js b/plugins/visage/visage.js new file mode 100644 index 00000000..3f129588 --- /dev/null +++ b/plugins/visage/visage.js @@ -0,0 +1,11811 @@ +(function () { + 'use strict'; + + let VISAGE_API_URL = "https://cc1234-stashface.hf.space/api/predict"; + let THRESHOLD = 20.0; // remove matches with a distance higher than this + let MAX_RESULTS = 12; // number of results to show, don't change this for now + + + function waitForElm(selector) { + return new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)); + } + + const observer = new MutationObserver((mutations) => { + if (document.querySelector(selector)) { + resolve(document.querySelector(selector)); + observer.disconnect(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + }); + } + + function getScenarioAndID() { + var result = document.URL.match(/(scenes|images)\/(\d+)/); + var scenario = result[1]; + var scenario_id = result[2]; + return [scenario, scenario_id]; + } + + async function getPerformers(performer_id) { + const reqData = { + query: `{ + findPerformers( performer_filter: {stash_id_endpoint: {endpoint: "", stash_id: "${performer_id}", modifier: EQUALS}}){ + performers { + name + id + } + } + }`, + }; + var results = await stash.callGQL(reqData); + return results.data.findPerformers.performers; + } + + async function getPerformersForScene(scene_id) { + const reqData = { + query: `{ + findScene(id: "${scene_id}") { + performers { + id + } + } + }`, + }; + var result = await stash.callGQL(reqData); + return result.data.findScene.performers.map((p) => p.id); + } + + + async function getPerformersForImage(image_id) { + const reqData = { + query: `{ + findImage(id: "${image_id}") { + performers { + id + } + } + }`, + }; + var result = await stash.callGQL(reqData); + return result.data.findImage.performers.map((p) => p.id); + } + + + async function updateScene(scene_id, performer_ids) { + const reqData = { + variables: { input: { id: scene_id, performer_ids: performer_ids } }, + query: `mutation sceneUpdate($input: SceneUpdateInput!){ + sceneUpdate(input: $input) { + id + } + }`, + }; + return stash.callGQL(reqData); + } + + + async function updateImage(image_id, performer_ids) { + const reqData = { + variables: { input: { id: image_id, performer_ids: performer_ids } }, + query: `mutation imageUpdate($input: ImageUpdateInput!){ + imageUpdate(input: $input) { + id + } + }`, + }; + return stash.callGQL(reqData); + } + + async function getStashboxEndpoint() { + const reqData = { + query: `{ + configuration { + general { + stashBoxes { + endpoint + } + } + } + }`, + }; + var result = await stash.callGQL(reqData); + return result.data.configuration.general.stashBoxes[0].endpoint; + } + + async function getPerformerDataFromStashID(stash_id) { + const reqData = { + variables: { + source: { + stash_box_index: 0, + }, + input: { + query: stash_id, + }, + }, + query: `query ScrapeSinglePerformer($source: ScraperSourceInput!, $input: ScrapeSinglePerformerInput!) { + scrapeSinglePerformer(source: $source, input: $input) { + name + disambiguation + gender + url + twitter + instagram + birthdate + ethnicity + country + eye_color + height + measurements + fake_tits + career_length + tattoos + piercings + aliases + images + details + death_date + hair_color + weight + remote_site_id + } + }`, + }; + var result = await stash.callGQL(reqData); + return result.data.scrapeSinglePerformer.filter( + (p) => p.remote_site_id === stash_id + )[0]; + } + + async function createPerformer(performer) { + const reqData = { + variables: { input: performer }, + query: `mutation performerCreate($input: PerformerCreateInput!) { + performerCreate(input: $input){ + id + } + }`, + }; + return stash.callGQL(reqData); + } + + function smoothload(node) { + function load() { + if (node.naturalWidth) return; // already loaded + + node.style.opacity = '0'; + node.style.transition = 'opacity 0.4s'; + + node.addEventListener( + 'load', + () => { + node.style.opacity = '1'; + }, + { + once: true + } + ); + } + + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.attributeName === 'src') { + load(); + } + } + }); + + observer.observe(node, { + attributes: true + }); + + load(); + + return { + destroy() { + observer.disconnect(); + } + }; + } + + /** + * Retrieves the URL of the sprite for a given scene ID. + * @param {number} scene_id - The ID of the scene to retrieve the sprite URL for. + * @returns {Promise} - A Promise that resolves with the sprite URL if it exists, or null if it does not. + */ + async function getUrlSprite(scene_id) { + const reqData = { + query: `{ + findScene(id: ${scene_id}){ + paths{ + sprite + } + } + }`, + }; + var result = await stash.callGQL(reqData); + const url = result.data.findScene.paths["sprite"]; + const response = await fetch(url); + if (response.status === 404) { + return null; + } else { + return result.data.findScene.paths["sprite"]; + } + } + + function noop() { } + const identity = x => x; + function assign(tar, src) { + // @ts-ignore + for (const k in src) + tar[k] = src[k]; + return tar; + } + function add_location(element, file, line, column, char) { + element.__svelte_meta = { + loc: { file, line, column, char } + }; + } + function run(fn) { + return fn(); + } + function blank_object() { + return Object.create(null); + } + function run_all(fns) { + fns.forEach(run); + } + function is_function(thing) { + return typeof thing === 'function'; + } + function safe_not_equal(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); + } + let src_url_equal_anchor; + function src_url_equal(element_src, url) { + if (!src_url_equal_anchor) { + src_url_equal_anchor = document.createElement('a'); + } + src_url_equal_anchor.href = url; + return element_src === src_url_equal_anchor.href; + } + function is_empty(obj) { + return Object.keys(obj).length === 0; + } + function validate_store(store, name) { + if (store != null && typeof store.subscribe !== 'function') { + throw new Error(`'${name}' is not a store with a 'subscribe' method`); + } + } + function subscribe(store, ...callbacks) { + if (store == null) { + return noop; + } + const unsub = store.subscribe(...callbacks); + return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; + } + function component_subscribe(component, store, callback) { + component.$$.on_destroy.push(subscribe(store, callback)); + } + function create_slot(definition, ctx, $$scope, fn) { + if (definition) { + const slot_ctx = get_slot_context(definition, ctx, $$scope, fn); + return definition[0](slot_ctx); + } + } + function get_slot_context(definition, ctx, $$scope, fn) { + return definition[1] && fn + ? assign($$scope.ctx.slice(), definition[1](fn(ctx))) + : $$scope.ctx; + } + function get_slot_changes(definition, $$scope, dirty, fn) { + if (definition[2] && fn) { + const lets = definition[2](fn(dirty)); + if ($$scope.dirty === undefined) { + return lets; + } + if (typeof lets === 'object') { + const merged = []; + const len = Math.max($$scope.dirty.length, lets.length); + for (let i = 0; i < len; i += 1) { + merged[i] = $$scope.dirty[i] | lets[i]; + } + return merged; + } + return $$scope.dirty | lets; + } + return $$scope.dirty; + } + function update_slot_base(slot, slot_definition, ctx, $$scope, slot_changes, get_slot_context_fn) { + if (slot_changes) { + const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn); + slot.p(slot_context, slot_changes); + } + } + function get_all_dirty_from_scope($$scope) { + if ($$scope.ctx.length > 32) { + const dirty = []; + const length = $$scope.ctx.length / 32; + for (let i = 0; i < length; i++) { + dirty[i] = -1; + } + return dirty; + } + return -1; + } + function action_destroyer(action_result) { + return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; + } + function split_css_unit(value) { + const split = typeof value === 'string' && value.match(/^\s*(-?[\d.]+)([^\s]*)\s*$/); + return split ? [parseFloat(split[1]), split[2] || 'px'] : [value, 'px']; + } + + const is_client = typeof window !== 'undefined'; + let now = is_client + ? () => window.performance.now() + : () => Date.now(); + let raf = is_client ? cb => requestAnimationFrame(cb) : noop; + + const tasks = new Set(); + function run_tasks(now) { + tasks.forEach(task => { + if (!task.c(now)) { + tasks.delete(task); + task.f(); + } + }); + if (tasks.size !== 0) + raf(run_tasks); + } + /** + * Creates a new task that runs on each raf frame + * until it returns a falsy value or is aborted + */ + function loop(callback) { + let task; + if (tasks.size === 0) + raf(run_tasks); + return { + promise: new Promise(fulfill => { + tasks.add(task = { c: callback, f: fulfill }); + }), + abort() { + tasks.delete(task); + } + }; + } + + const globals = (typeof window !== 'undefined' + ? window + : typeof globalThis !== 'undefined' + ? globalThis + : global); + function append(target, node) { + target.appendChild(node); + } + function get_root_for_style(node) { + if (!node) + return document; + const root = node.getRootNode ? node.getRootNode() : node.ownerDocument; + if (root && root.host) { + return root; + } + return node.ownerDocument; + } + function append_empty_stylesheet(node) { + const style_element = element('style'); + append_stylesheet(get_root_for_style(node), style_element); + return style_element.sheet; + } + function append_stylesheet(node, style) { + append(node.head || node, style); + return style.sheet; + } + function insert(target, node, anchor) { + target.insertBefore(node, anchor || null); + } + function detach(node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + function element(name) { + return document.createElement(name); + } + function svg_element(name) { + return document.createElementNS('http://www.w3.org/2000/svg', name); + } + function text(data) { + return document.createTextNode(data); + } + function space() { + return text(' '); + } + function empty() { + return text(''); + } + function listen(node, event, handler, options) { + node.addEventListener(event, handler, options); + return () => node.removeEventListener(event, handler, options); + } + function attr(node, attribute, value) { + if (value == null) + node.removeAttribute(attribute); + else if (node.getAttribute(attribute) !== value) + node.setAttribute(attribute, value); + } + function children(element) { + return Array.from(element.childNodes); + } + function set_style(node, key, value, important) { + if (value == null) { + node.style.removeProperty(key); + } + else { + node.style.setProperty(key, value, important ? 'important' : ''); + } + } + function toggle_class(element, name, toggle) { + element.classList[toggle ? 'add' : 'remove'](name); + } + function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) { + const e = document.createEvent('CustomEvent'); + e.initCustomEvent(type, bubbles, cancelable, detail); + return e; + } + + // we need to store the information for multiple documents because a Svelte application could also contain iframes + // https://github.com/sveltejs/svelte/issues/3624 + const managed_styles = new Map(); + let active = 0; + // https://github.com/darkskyapp/string-hash/blob/master/index.js + function hash(str) { + let hash = 5381; + let i = str.length; + while (i--) + hash = ((hash << 5) - hash) ^ str.charCodeAt(i); + return hash >>> 0; + } + function create_style_information(doc, node) { + const info = { stylesheet: append_empty_stylesheet(node), rules: {} }; + managed_styles.set(doc, info); + return info; + } + function create_rule(node, a, b, duration, delay, ease, fn, uid = 0) { + const step = 16.666 / duration; + let keyframes = '{\n'; + for (let p = 0; p <= 1; p += step) { + const t = a + (b - a) * ease(p); + keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`; + } + const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; + const name = `__svelte_${hash(rule)}_${uid}`; + const doc = get_root_for_style(node); + const { stylesheet, rules } = managed_styles.get(doc) || create_style_information(doc, node); + if (!rules[name]) { + rules[name] = true; + stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); + } + const animation = node.style.animation || ''; + node.style.animation = `${animation ? `${animation}, ` : ''}${name} ${duration}ms linear ${delay}ms 1 both`; + active += 1; + return name; + } + function delete_rule(node, name) { + const previous = (node.style.animation || '').split(', '); + const next = previous.filter(name + ? anim => anim.indexOf(name) < 0 // remove specific animation + : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations + ); + const deleted = previous.length - next.length; + if (deleted) { + node.style.animation = next.join(', '); + active -= deleted; + if (!active) + clear_rules(); + } + } + function clear_rules() { + raf(() => { + if (active) + return; + managed_styles.forEach(info => { + const { ownerNode } = info.stylesheet; + // there is no ownerNode if it runs on jsdom. + if (ownerNode) + detach(ownerNode); + }); + managed_styles.clear(); + }); + } + + function create_animation(node, from, fn, params) { + if (!from) + return noop; + const to = node.getBoundingClientRect(); + if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) + return noop; + const { delay = 0, duration = 300, easing = identity, + // @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation? + start: start_time = now() + delay, + // @ts-ignore todo: + end = start_time + duration, tick = noop, css } = fn(node, { from, to }, params); + let running = true; + let started = false; + let name; + function start() { + if (css) { + name = create_rule(node, 0, 1, duration, delay, easing, css); + } + if (!delay) { + started = true; + } + } + function stop() { + if (css) + delete_rule(node, name); + running = false; + } + loop(now => { + if (!started && now >= start_time) { + started = true; + } + if (started && now >= end) { + tick(1, 0); + stop(); + } + if (!running) { + return false; + } + if (started) { + const p = now - start_time; + const t = 0 + 1 * easing(p / duration); + tick(t, 1 - t); + } + return true; + }); + start(); + tick(0, 1); + return stop; + } + function fix_position(node) { + const style = getComputedStyle(node); + if (style.position !== 'absolute' && style.position !== 'fixed') { + const { width, height } = style; + const a = node.getBoundingClientRect(); + node.style.position = 'absolute'; + node.style.width = width; + node.style.height = height; + add_transform(node, a); + } + } + function add_transform(node, a) { + const b = node.getBoundingClientRect(); + if (a.left !== b.left || a.top !== b.top) { + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`; + } + } + + let current_component; + function set_current_component(component) { + current_component = component; + } + function get_current_component() { + if (!current_component) + throw new Error('Function called outside component initialization'); + return current_component; + } + /** + * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. + * It must be called during the component's initialisation (but doesn't need to live *inside* the component; + * it can be called from an external module). + * + * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). + * + * https://svelte.dev/docs#run-time-svelte-onmount + */ + function onMount(fn) { + get_current_component().$$.on_mount.push(fn); + } + /** + * Schedules a callback to run immediately after the component has been updated. + * + * The first time the callback runs will be after the initial `onMount` + */ + function afterUpdate(fn) { + get_current_component().$$.after_update.push(fn); + } + /** + * Schedules a callback to run immediately before the component is unmounted. + * + * Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the + * only one that runs inside a server-side component. + * + * https://svelte.dev/docs#run-time-svelte-ondestroy + */ + function onDestroy(fn) { + get_current_component().$$.on_destroy.push(fn); + } + /** + * Associates an arbitrary `context` object with the current component and the specified `key` + * and returns that object. The context is then available to children of the component + * (including slotted content) with `getContext`. + * + * Like lifecycle functions, this must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-setcontext + */ + function setContext(key, context) { + get_current_component().$$.context.set(key, context); + return context; + } + /** + * Retrieves the context that belongs to the closest parent component with the specified `key`. + * Must be called during component initialisation. + * + * https://svelte.dev/docs#run-time-svelte-getcontext + */ + function getContext(key) { + return get_current_component().$$.context.get(key); + } + + const dirty_components = []; + const binding_callbacks = []; + let render_callbacks = []; + const flush_callbacks = []; + const resolved_promise = /* @__PURE__ */ Promise.resolve(); + let update_scheduled = false; + function schedule_update() { + if (!update_scheduled) { + update_scheduled = true; + resolved_promise.then(flush); + } + } + function tick() { + schedule_update(); + return resolved_promise; + } + function add_render_callback(fn) { + render_callbacks.push(fn); + } + function add_flush_callback(fn) { + flush_callbacks.push(fn); + } + // flush() calls callbacks in this order: + // 1. All beforeUpdate callbacks, in order: parents before children + // 2. All bind:this callbacks, in reverse order: children before parents. + // 3. All afterUpdate callbacks, in order: parents before children. EXCEPT + // for afterUpdates called during the initial onMount, which are called in + // reverse order: children before parents. + // Since callbacks might update component values, which could trigger another + // call to flush(), the following steps guard against this: + // 1. During beforeUpdate, any updated components will be added to the + // dirty_components array and will cause a reentrant call to flush(). Because + // the flush index is kept outside the function, the reentrant call will pick + // up where the earlier call left off and go through all dirty components. The + // current_component value is saved and restored so that the reentrant call will + // not interfere with the "parent" flush() call. + // 2. bind:this callbacks cannot trigger new flush() calls. + // 3. During afterUpdate, any updated components will NOT have their afterUpdate + // callback called a second time; the seen_callbacks set, outside the flush() + // function, guarantees this behavior. + const seen_callbacks = new Set(); + let flushidx = 0; // Do *not* move this inside the flush() function + function flush() { + // Do not reenter flush while dirty components are updated, as this can + // result in an infinite loop. Instead, let the inner flush handle it. + // Reentrancy is ok afterwards for bindings etc. + if (flushidx !== 0) { + return; + } + const saved_component = current_component; + do { + // first, call beforeUpdate functions + // and update components + try { + while (flushidx < dirty_components.length) { + const component = dirty_components[flushidx]; + flushidx++; + set_current_component(component); + update(component.$$); + } + } + catch (e) { + // reset dirty state to not end up in a deadlocked state and then rethrow + dirty_components.length = 0; + flushidx = 0; + throw e; + } + set_current_component(null); + dirty_components.length = 0; + flushidx = 0; + while (binding_callbacks.length) + binding_callbacks.pop()(); + // then, once components are updated, call + // afterUpdate functions. This may cause + // subsequent updates... + for (let i = 0; i < render_callbacks.length; i += 1) { + const callback = render_callbacks[i]; + if (!seen_callbacks.has(callback)) { + // ...so guard against infinite loops + seen_callbacks.add(callback); + callback(); + } + } + render_callbacks.length = 0; + } while (dirty_components.length); + while (flush_callbacks.length) { + flush_callbacks.pop()(); + } + update_scheduled = false; + seen_callbacks.clear(); + set_current_component(saved_component); + } + function update($$) { + if ($$.fragment !== null) { + $$.update(); + run_all($$.before_update); + const dirty = $$.dirty; + $$.dirty = [-1]; + $$.fragment && $$.fragment.p($$.ctx, dirty); + $$.after_update.forEach(add_render_callback); + } + } + /** + * Useful for example to execute remaining `afterUpdate` callbacks before executing `destroy`. + */ + function flush_render_callbacks(fns) { + const filtered = []; + const targets = []; + render_callbacks.forEach((c) => fns.indexOf(c) === -1 ? filtered.push(c) : targets.push(c)); + targets.forEach((c) => c()); + render_callbacks = filtered; + } + + let promise; + function wait() { + if (!promise) { + promise = Promise.resolve(); + promise.then(() => { + promise = null; + }); + } + return promise; + } + function dispatch(node, direction, kind) { + node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`)); + } + const outroing = new Set(); + let outros; + function group_outros() { + outros = { + r: 0, + c: [], + p: outros // parent group + }; + } + function check_outros() { + if (!outros.r) { + run_all(outros.c); + } + outros = outros.p; + } + function transition_in(block, local) { + if (block && block.i) { + outroing.delete(block); + block.i(local); + } + } + function transition_out(block, local, detach, callback) { + if (block && block.o) { + if (outroing.has(block)) + return; + outroing.add(block); + outros.c.push(() => { + outroing.delete(block); + if (callback) { + if (detach) + block.d(1); + callback(); + } + }); + block.o(local); + } + else if (callback) { + callback(); + } + } + const null_transition = { duration: 0 }; + function create_in_transition(node, fn, params) { + const options = { direction: 'in' }; + let config = fn(node, params, options); + let running = false; + let animation_name; + let task; + let uid = 0; + function cleanup() { + if (animation_name) + delete_rule(node, animation_name); + } + function go() { + const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition; + if (css) + animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++); + tick(0, 1); + const start_time = now() + delay; + const end_time = start_time + duration; + if (task) + task.abort(); + running = true; + add_render_callback(() => dispatch(node, true, 'start')); + task = loop(now => { + if (running) { + if (now >= end_time) { + tick(1, 0); + dispatch(node, true, 'end'); + cleanup(); + return running = false; + } + if (now >= start_time) { + const t = easing((now - start_time) / duration); + tick(t, 1 - t); + } + } + return running; + }); + } + let started = false; + return { + start() { + if (started) + return; + started = true; + delete_rule(node); + if (is_function(config)) { + config = config(options); + wait().then(go); + } + else { + go(); + } + }, + invalidate() { + started = false; + }, + end() { + if (running) { + cleanup(); + running = false; + } + } + }; + } + function create_out_transition(node, fn, params) { + const options = { direction: 'out' }; + let config = fn(node, params, options); + let running = true; + let animation_name; + const group = outros; + group.r += 1; + function go() { + const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition; + if (css) + animation_name = create_rule(node, 1, 0, duration, delay, easing, css); + const start_time = now() + delay; + const end_time = start_time + duration; + add_render_callback(() => dispatch(node, false, 'start')); + loop(now => { + if (running) { + if (now >= end_time) { + tick(0, 1); + dispatch(node, false, 'end'); + if (!--group.r) { + // this will result in `end()` being called, + // so we don't need to clean up here + run_all(group.c); + } + return false; + } + if (now >= start_time) { + const t = easing((now - start_time) / duration); + tick(1 - t, t); + } + } + return running; + }); + } + if (is_function(config)) { + wait().then(() => { + // @ts-ignore + config = config(options); + go(); + }); + } + else { + go(); + } + return { + end(reset) { + if (reset && config.tick) { + config.tick(1, 0); + } + if (running) { + if (animation_name) + delete_rule(node, animation_name); + running = false; + } + } + }; + } + + function destroy_block(block, lookup) { + block.d(1); + lookup.delete(block.key); + } + function outro_and_destroy_block(block, lookup) { + transition_out(block, 1, 1, () => { + lookup.delete(block.key); + }); + } + function fix_and_outro_and_destroy_block(block, lookup) { + block.f(); + outro_and_destroy_block(block, lookup); + } + function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) { + let o = old_blocks.length; + let n = list.length; + let i = o; + const old_indexes = {}; + while (i--) + old_indexes[old_blocks[i].key] = i; + const new_blocks = []; + const new_lookup = new Map(); + const deltas = new Map(); + const updates = []; + i = n; + while (i--) { + const child_ctx = get_context(ctx, list, i); + const key = get_key(child_ctx); + let block = lookup.get(key); + if (!block) { + block = create_each_block(key, child_ctx); + block.c(); + } + else if (dynamic) { + // defer updates until all the DOM shuffling is done + updates.push(() => block.p(child_ctx, dirty)); + } + new_lookup.set(key, new_blocks[i] = block); + if (key in old_indexes) + deltas.set(key, Math.abs(i - old_indexes[key])); + } + const will_move = new Set(); + const did_move = new Set(); + function insert(block) { + transition_in(block, 1); + block.m(node, next); + lookup.set(block.key, block); + next = block.first; + n--; + } + while (o && n) { + const new_block = new_blocks[n - 1]; + const old_block = old_blocks[o - 1]; + const new_key = new_block.key; + const old_key = old_block.key; + if (new_block === old_block) { + // do nothing + next = new_block.first; + o--; + n--; + } + else if (!new_lookup.has(old_key)) { + // remove old block + destroy(old_block, lookup); + o--; + } + else if (!lookup.has(new_key) || will_move.has(new_key)) { + insert(new_block); + } + else if (did_move.has(old_key)) { + o--; + } + else if (deltas.get(new_key) > deltas.get(old_key)) { + did_move.add(new_key); + insert(new_block); + } + else { + will_move.add(old_key); + o--; + } + } + while (o--) { + const old_block = old_blocks[o]; + if (!new_lookup.has(old_block.key)) + destroy(old_block, lookup); + } + while (n) + insert(new_blocks[n - 1]); + run_all(updates); + return new_blocks; + } + function validate_each_keys(ctx, list, get_context, get_key) { + const keys = new Set(); + for (let i = 0; i < list.length; i++) { + const key = get_key(get_context(ctx, list, i)); + if (keys.has(key)) { + throw new Error('Cannot have duplicate keys in a keyed each'); + } + keys.add(key); + } + } + + function bind(component, name, callback) { + const index = component.$$.props[name]; + if (index !== undefined) { + component.$$.bound[index] = callback; + callback(component.$$.ctx[index]); + } + } + function create_component(block) { + block && block.c(); + } + function mount_component(component, target, anchor, customElement) { + const { fragment, after_update } = component.$$; + fragment && fragment.m(target, anchor); + if (!customElement) { + // onMount happens before the initial afterUpdate + add_render_callback(() => { + const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); + // if the component was destroyed immediately + // it will update the `$$.on_destroy` reference to `null`. + // the destructured on_destroy may still reference to the old array + if (component.$$.on_destroy) { + component.$$.on_destroy.push(...new_on_destroy); + } + else { + // Edge case - component was destroyed immediately, + // most likely as a result of a binding initialising + run_all(new_on_destroy); + } + component.$$.on_mount = []; + }); + } + after_update.forEach(add_render_callback); + } + function destroy_component(component, detaching) { + const $$ = component.$$; + if ($$.fragment !== null) { + flush_render_callbacks($$.after_update); + run_all($$.on_destroy); + $$.fragment && $$.fragment.d(detaching); + // TODO null out other refs, including component.$$ (but need to + // preserve final state?) + $$.on_destroy = $$.fragment = null; + $$.ctx = []; + } + } + function make_dirty(component, i) { + if (component.$$.dirty[0] === -1) { + dirty_components.push(component); + schedule_update(); + component.$$.dirty.fill(0); + } + component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); + } + function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) { + const parent_component = current_component; + set_current_component(component); + const $$ = component.$$ = { + fragment: null, + ctx: [], + // state + props, + update: noop, + not_equal, + bound: blank_object(), + // lifecycle + on_mount: [], + on_destroy: [], + on_disconnect: [], + before_update: [], + after_update: [], + context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), + // everything else + callbacks: blank_object(), + dirty, + skip_bound: false, + root: options.target || parent_component.$$.root + }; + append_styles && append_styles($$.root); + let ready = false; + $$.ctx = instance + ? instance(component, options.props || {}, (i, ret, ...rest) => { + const value = rest.length ? rest[0] : ret; + if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { + if (!$$.skip_bound && $$.bound[i]) + $$.bound[i](value); + if (ready) + make_dirty(component, i); + } + return ret; + }) + : []; + $$.update(); + ready = true; + run_all($$.before_update); + // `false` as a special case of no DOM component + $$.fragment = create_fragment ? create_fragment($$.ctx) : false; + if (options.target) { + if (options.hydrate) { + const nodes = children(options.target); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + $$.fragment && $$.fragment.l(nodes); + nodes.forEach(detach); + } + else { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + $$.fragment && $$.fragment.c(); + } + if (options.intro) + transition_in(component.$$.fragment); + mount_component(component, options.target, options.anchor, options.customElement); + flush(); + } + set_current_component(parent_component); + } + /** + * Base class for Svelte components. Used when dev=false. + */ + class SvelteComponent { + $destroy() { + destroy_component(this, 1); + this.$destroy = noop; + } + $on(type, callback) { + if (!is_function(callback)) { + return noop; + } + const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); + callbacks.push(callback); + return () => { + const index = callbacks.indexOf(callback); + if (index !== -1) + callbacks.splice(index, 1); + }; + } + $set($$props) { + if (this.$$set && !is_empty($$props)) { + this.$$.skip_bound = true; + this.$$set($$props); + this.$$.skip_bound = false; + } + } + } + + function dispatch_dev(type, detail) { + document.dispatchEvent(custom_event(type, Object.assign({ version: '3.59.2' }, detail), { bubbles: true })); + } + function append_dev(target, node) { + dispatch_dev('SvelteDOMInsert', { target, node }); + append(target, node); + } + function insert_dev(target, node, anchor) { + dispatch_dev('SvelteDOMInsert', { target, node, anchor }); + insert(target, node, anchor); + } + function detach_dev(node) { + dispatch_dev('SvelteDOMRemove', { node }); + detach(node); + } + function listen_dev(node, event, handler, options, has_prevent_default, has_stop_propagation, has_stop_immediate_propagation) { + const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : []; + if (has_prevent_default) + modifiers.push('preventDefault'); + if (has_stop_propagation) + modifiers.push('stopPropagation'); + if (has_stop_immediate_propagation) + modifiers.push('stopImmediatePropagation'); + dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers }); + const dispose = listen(node, event, handler, options); + return () => { + dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers }); + dispose(); + }; + } + function attr_dev(node, attribute, value) { + attr(node, attribute, value); + if (value == null) + dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute }); + else + dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value }); + } + function set_data_dev(text, data) { + data = '' + data; + if (text.data === data) + return; + dispatch_dev('SvelteDOMSetData', { node: text, data }); + text.data = data; + } + function validate_each_argument(arg) { + if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { + let msg = '{#each} only iterates over array-like objects.'; + if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { + msg += ' You can use a spread to convert this iterable into an array.'; + } + throw new Error(msg); + } + } + function validate_slots(name, slot, keys) { + for (const slot_key of Object.keys(slot)) { + if (!~keys.indexOf(slot_key)) { + console.warn(`<${name}> received an unexpected slot "${slot_key}".`); + } + } + } + /** + * Base class for Svelte components with some minor dev-enhancements. Used when dev=true. + */ + class SvelteComponentDev extends SvelteComponent { + constructor(options) { + if (!options || (!options.target && !options.$$inline)) { + throw new Error("'target' is a required option"); + } + super(); + } + $destroy() { + super.$destroy(); + this.$destroy = () => { + console.warn('Component was already destroyed'); // eslint-disable-line no-console + }; + } + $capture_state() { } + $inject_state() { } + } + + let id = 1; + + function getId() { + return `svelte-tabs-${id++}`; + } + + const subscriber_queue = []; + /** + * Create a `Writable` store that allows both updating and reading by subscription. + * @param {*=}value initial value + * @param {StartStopNotifier=} start + */ + function writable(value, start = noop) { + let stop; + const subscribers = new Set(); + function set(new_value) { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (stop) { // store is ready + const run_queue = !subscriber_queue.length; + for (const subscriber of subscribers) { + subscriber[1](); + subscriber_queue.push(subscriber, value); + } + if (run_queue) { + for (let i = 0; i < subscriber_queue.length; i += 2) { + subscriber_queue[i][0](subscriber_queue[i + 1]); + } + subscriber_queue.length = 0; + } + } + } + } + function update(fn) { + set(fn(value)); + } + function subscribe(run, invalidate = noop) { + const subscriber = [run, invalidate]; + subscribers.add(subscriber); + if (subscribers.size === 1) { + stop = start(set) || noop; + } + run(value); + return () => { + subscribers.delete(subscriber); + if (subscribers.size === 0 && stop) { + stop(); + stop = null; + } + }; + } + return { set, update, subscribe }; + } + + /* node_modules/svelte-tabs/src/Tabs.svelte generated by Svelte v3.59.2 */ + const file$7 = "node_modules/svelte-tabs/src/Tabs.svelte"; + + function create_fragment$7(ctx) { + let div; + let current; + let mounted; + let dispose; + const default_slot_template = /*#slots*/ ctx[4].default; + const default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[3], null); + + const block = { + c: function create() { + div = element("div"); + if (default_slot) default_slot.c(); + attr_dev(div, "class", "svelte-tabs"); + add_location(div, file$7, 97, 0, 2405); + }, + l: function claim(nodes) { + throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); + }, + m: function mount(target, anchor) { + insert_dev(target, div, anchor); + + if (default_slot) { + default_slot.m(div, null); + } + + current = true; + + if (!mounted) { + dispose = listen_dev(div, "keydown", /*handleKeyDown*/ ctx[1], false, false, false, false); + mounted = true; + } + }, + p: function update(ctx, [dirty]) { + if (default_slot) { + if (default_slot.p && (!current || dirty & /*$$scope*/ 8)) { + update_slot_base( + default_slot, + default_slot_template, + ctx, + /*$$scope*/ ctx[3], + !current + ? get_all_dirty_from_scope(/*$$scope*/ ctx[3]) + : get_slot_changes(default_slot_template, /*$$scope*/ ctx[3], dirty, null), + null + ); + } + } + }, + i: function intro(local) { + if (current) return; + transition_in(default_slot, local); + current = true; + }, + o: function outro(local) { + transition_out(default_slot, local); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(div); + if (default_slot) default_slot.d(detaching); + mounted = false; + dispose(); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_fragment$7.name, + type: "component", + source: "", + ctx + }); + + return block; + } + + const TABS = {}; + + function removeAndUpdateSelected(arr, item, selectedStore) { + const index = arr.indexOf(item); + arr.splice(index, 1); + + selectedStore.update(selected => selected === item + ? arr[index] || arr[arr.length - 1] + : selected); + } + + function instance$7($$self, $$props, $$invalidate) { + let $selectedTab; + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots('Tabs', slots, ['default']); + let { initialSelectedIndex = 0 } = $$props; + const tabElements = []; + const tabs = []; + const panels = []; + const controls = writable({}); + const labeledBy = writable({}); + const selectedTab = writable(null); + validate_store(selectedTab, 'selectedTab'); + component_subscribe($$self, selectedTab, value => $$invalidate(5, $selectedTab = value)); + const selectedPanel = writable(null); + + function registerItem(arr, item, selectedStore) { + arr.push(item); + selectedStore.update(selected => selected || item); + onDestroy(() => removeAndUpdateSelected(arr, item, selectedStore)); + } + + function selectTab(tab) { + const index = tabs.indexOf(tab); + selectedTab.set(tab); + selectedPanel.set(panels[index]); + } + + setContext(TABS, { + registerTab(tab) { + registerItem(tabs, tab, selectedTab); + }, + registerTabElement(tabElement) { + tabElements.push(tabElement); + }, + registerPanel(panel) { + registerItem(panels, panel, selectedPanel); + }, + selectTab, + selectedTab, + selectedPanel, + controls, + labeledBy + }); + + onMount(() => { + selectTab(tabs[initialSelectedIndex]); + }); + + afterUpdate(() => { + for (let i = 0; i < tabs.length; i++) { + controls.update(controlsData => ({ + ...controlsData, + [tabs[i].id]: panels[i].id + })); + + labeledBy.update(labeledByData => ({ + ...labeledByData, + [panels[i].id]: tabs[i].id + })); + } + }); + + async function handleKeyDown(event) { + if (event.target.classList.contains('svelte-tabs__tab')) { + let selectedIndex = tabs.indexOf($selectedTab); + + switch (event.key) { + case 'ArrowRight': + selectedIndex += 1; + if (selectedIndex > tabs.length - 1) { + selectedIndex = 0; + } + selectTab(tabs[selectedIndex]); + tabElements[selectedIndex].focus(); + break; + case 'ArrowLeft': + selectedIndex -= 1; + if (selectedIndex < 0) { + selectedIndex = tabs.length - 1; + } + selectTab(tabs[selectedIndex]); + tabElements[selectedIndex].focus(); + } + } + } + + const writable_props = ['initialSelectedIndex']; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(` was created with unknown prop '${key}'`); + }); + + $$self.$$set = $$props => { + if ('initialSelectedIndex' in $$props) $$invalidate(2, initialSelectedIndex = $$props.initialSelectedIndex); + if ('$$scope' in $$props) $$invalidate(3, $$scope = $$props.$$scope); + }; + + $$self.$capture_state = () => ({ + TABS, + afterUpdate, + setContext, + onDestroy, + onMount, + tick, + writable, + initialSelectedIndex, + tabElements, + tabs, + panels, + controls, + labeledBy, + selectedTab, + selectedPanel, + removeAndUpdateSelected, + registerItem, + selectTab, + handleKeyDown, + $selectedTab + }); + + $$self.$inject_state = $$props => { + if ('initialSelectedIndex' in $$props) $$invalidate(2, initialSelectedIndex = $$props.initialSelectedIndex); + }; + + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + + return [selectedTab, handleKeyDown, initialSelectedIndex, $$scope, slots]; + } + + class Tabs extends SvelteComponentDev { + constructor(options) { + super(options); + init(this, options, instance$7, create_fragment$7, safe_not_equal, { initialSelectedIndex: 2 }); + + dispatch_dev("SvelteRegisterComponent", { + component: this, + tagName: "Tabs", + options, + id: create_fragment$7.name + }); + } + + get initialSelectedIndex() { + throw new Error(": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''"); + } + + set initialSelectedIndex(value) { + throw new Error(": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''"); + } + } + + /* node_modules/svelte-tabs/src/Tab.svelte generated by Svelte v3.59.2 */ + const file$6 = "node_modules/svelte-tabs/src/Tab.svelte"; + + function create_fragment$6(ctx) { + let li; + let li_aria_controls_value; + let li_tabindex_value; + let current; + let mounted; + let dispose; + const default_slot_template = /*#slots*/ ctx[9].default; + const default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[8], null); + + const block = { + c: function create() { + li = element("li"); + if (default_slot) default_slot.c(); + attr_dev(li, "role", "tab"); + attr_dev(li, "id", /*tab*/ ctx[3].id); + attr_dev(li, "aria-controls", li_aria_controls_value = /*$controls*/ ctx[2][/*tab*/ ctx[3].id]); + attr_dev(li, "aria-selected", /*isSelected*/ ctx[1]); + attr_dev(li, "tabindex", li_tabindex_value = /*isSelected*/ ctx[1] ? 0 : -1); + attr_dev(li, "class", "svelte-tabs__tab svelte-1fbofsd"); + toggle_class(li, "svelte-tabs__selected", /*isSelected*/ ctx[1]); + add_location(li, file$6, 45, 0, 812); + }, + l: function claim(nodes) { + throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); + }, + m: function mount(target, anchor) { + insert_dev(target, li, anchor); + + if (default_slot) { + default_slot.m(li, null); + } + + /*li_binding*/ ctx[10](li); + current = true; + + if (!mounted) { + dispose = listen_dev(li, "click", /*click_handler*/ ctx[11], false, false, false, false); + mounted = true; + } + }, + p: function update(ctx, [dirty]) { + if (default_slot) { + if (default_slot.p && (!current || dirty & /*$$scope*/ 256)) { + update_slot_base( + default_slot, + default_slot_template, + ctx, + /*$$scope*/ ctx[8], + !current + ? get_all_dirty_from_scope(/*$$scope*/ ctx[8]) + : get_slot_changes(default_slot_template, /*$$scope*/ ctx[8], dirty, null), + null + ); + } + } + + if (!current || dirty & /*$controls*/ 4 && li_aria_controls_value !== (li_aria_controls_value = /*$controls*/ ctx[2][/*tab*/ ctx[3].id])) { + attr_dev(li, "aria-controls", li_aria_controls_value); + } + + if (!current || dirty & /*isSelected*/ 2) { + attr_dev(li, "aria-selected", /*isSelected*/ ctx[1]); + } + + if (!current || dirty & /*isSelected*/ 2 && li_tabindex_value !== (li_tabindex_value = /*isSelected*/ ctx[1] ? 0 : -1)) { + attr_dev(li, "tabindex", li_tabindex_value); + } + + if (!current || dirty & /*isSelected*/ 2) { + toggle_class(li, "svelte-tabs__selected", /*isSelected*/ ctx[1]); + } + }, + i: function intro(local) { + if (current) return; + transition_in(default_slot, local); + current = true; + }, + o: function outro(local) { + transition_out(default_slot, local); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(li); + if (default_slot) default_slot.d(detaching); + /*li_binding*/ ctx[10](null); + mounted = false; + dispose(); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_fragment$6.name, + type: "component", + source: "", + ctx + }); + + return block; + } + + function instance$6($$self, $$props, $$invalidate) { + let $selectedTab; + let $controls; + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots('Tab', slots, ['default']); + let tabEl; + const tab = { id: getId() }; + const { registerTab, registerTabElement, selectTab, selectedTab, controls } = getContext(TABS); + validate_store(selectedTab, 'selectedTab'); + component_subscribe($$self, selectedTab, value => $$invalidate(7, $selectedTab = value)); + validate_store(controls, 'controls'); + component_subscribe($$self, controls, value => $$invalidate(2, $controls = value)); + let isSelected; + registerTab(tab); + + onMount(async () => { + await tick(); + registerTabElement(tabEl); + }); + + const writable_props = []; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(` was created with unknown prop '${key}'`); + }); + + function li_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](() => { + tabEl = $$value; + $$invalidate(0, tabEl); + }); + } + + const click_handler = () => selectTab(tab); + + $$self.$$set = $$props => { + if ('$$scope' in $$props) $$invalidate(8, $$scope = $$props.$$scope); + }; + + $$self.$capture_state = () => ({ + getContext, + onMount, + tick, + getId, + TABS, + tabEl, + tab, + registerTab, + registerTabElement, + selectTab, + selectedTab, + controls, + isSelected, + $selectedTab, + $controls + }); + + $$self.$inject_state = $$props => { + if ('tabEl' in $$props) $$invalidate(0, tabEl = $$props.tabEl); + if ('isSelected' in $$props) $$invalidate(1, isSelected = $$props.isSelected); + }; + + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + + $$self.$$.update = () => { + if ($$self.$$.dirty & /*$selectedTab*/ 128) { + $$invalidate(1, isSelected = $selectedTab === tab); + } + }; + + return [ + tabEl, + isSelected, + $controls, + tab, + selectTab, + selectedTab, + controls, + $selectedTab, + $$scope, + slots, + li_binding, + click_handler + ]; + } + + class Tab extends SvelteComponentDev { + constructor(options) { + super(options); + init(this, options, instance$6, create_fragment$6, safe_not_equal, {}); + + dispatch_dev("SvelteRegisterComponent", { + component: this, + tagName: "Tab", + options, + id: create_fragment$6.name + }); + } + } + + /* node_modules/svelte-tabs/src/TabList.svelte generated by Svelte v3.59.2 */ + + const file$5 = "node_modules/svelte-tabs/src/TabList.svelte"; + + function create_fragment$5(ctx) { + let ul; + let current; + const default_slot_template = /*#slots*/ ctx[1].default; + const default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[0], null); + + const block = { + c: function create() { + ul = element("ul"); + if (default_slot) default_slot.c(); + attr_dev(ul, "role", "tablist"); + attr_dev(ul, "class", "svelte-tabs__tab-list svelte-12yby2a"); + add_location(ul, file$5, 8, 0, 116); + }, + l: function claim(nodes) { + throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); + }, + m: function mount(target, anchor) { + insert_dev(target, ul, anchor); + + if (default_slot) { + default_slot.m(ul, null); + } + + current = true; + }, + p: function update(ctx, [dirty]) { + if (default_slot) { + if (default_slot.p && (!current || dirty & /*$$scope*/ 1)) { + update_slot_base( + default_slot, + default_slot_template, + ctx, + /*$$scope*/ ctx[0], + !current + ? get_all_dirty_from_scope(/*$$scope*/ ctx[0]) + : get_slot_changes(default_slot_template, /*$$scope*/ ctx[0], dirty, null), + null + ); + } + } + }, + i: function intro(local) { + if (current) return; + transition_in(default_slot, local); + current = true; + }, + o: function outro(local) { + transition_out(default_slot, local); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(ul); + if (default_slot) default_slot.d(detaching); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_fragment$5.name, + type: "component", + source: "", + ctx + }); + + return block; + } + + function instance$5($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots('TabList', slots, ['default']); + const writable_props = []; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(` was created with unknown prop '${key}'`); + }); + + $$self.$$set = $$props => { + if ('$$scope' in $$props) $$invalidate(0, $$scope = $$props.$$scope); + }; + + return [$$scope, slots]; + } + + class TabList extends SvelteComponentDev { + constructor(options) { + super(options); + init(this, options, instance$5, create_fragment$5, safe_not_equal, {}); + + dispatch_dev("SvelteRegisterComponent", { + component: this, + tagName: "TabList", + options, + id: create_fragment$5.name + }); + } + } + + /* node_modules/svelte-tabs/src/TabPanel.svelte generated by Svelte v3.59.2 */ + const file$4 = "node_modules/svelte-tabs/src/TabPanel.svelte"; + + // (26:2) {#if $selectedPanel === panel} + function create_if_block$1(ctx) { + let current; + const default_slot_template = /*#slots*/ ctx[6].default; + const default_slot = create_slot(default_slot_template, ctx, /*$$scope*/ ctx[5], null); + + const block = { + c: function create() { + if (default_slot) default_slot.c(); + }, + m: function mount(target, anchor) { + if (default_slot) { + default_slot.m(target, anchor); + } + + current = true; + }, + p: function update(ctx, dirty) { + if (default_slot) { + if (default_slot.p && (!current || dirty & /*$$scope*/ 32)) { + update_slot_base( + default_slot, + default_slot_template, + ctx, + /*$$scope*/ ctx[5], + !current + ? get_all_dirty_from_scope(/*$$scope*/ ctx[5]) + : get_slot_changes(default_slot_template, /*$$scope*/ ctx[5], dirty, null), + null + ); + } + } + }, + i: function intro(local) { + if (current) return; + transition_in(default_slot, local); + current = true; + }, + o: function outro(local) { + transition_out(default_slot, local); + current = false; + }, + d: function destroy(detaching) { + if (default_slot) default_slot.d(detaching); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_if_block$1.name, + type: "if", + source: "(26:2) {#if $selectedPanel === panel}", + ctx + }); + + return block; + } + + function create_fragment$4(ctx) { + let div; + let div_aria_labelledby_value; + let current; + let if_block = /*$selectedPanel*/ ctx[1] === /*panel*/ ctx[2] && create_if_block$1(ctx); + + const block = { + c: function create() { + div = element("div"); + if (if_block) if_block.c(); + attr_dev(div, "id", /*panel*/ ctx[2].id); + attr_dev(div, "aria-labelledby", div_aria_labelledby_value = /*$labeledBy*/ ctx[0][/*panel*/ ctx[2].id]); + attr_dev(div, "class", "svelte-tabs__tab-panel svelte-epfyet"); + attr_dev(div, "role", "tabpanel"); + add_location(div, file$4, 20, 0, 338); + }, + l: function claim(nodes) { + throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); + }, + m: function mount(target, anchor) { + insert_dev(target, div, anchor); + if (if_block) if_block.m(div, null); + current = true; + }, + p: function update(ctx, [dirty]) { + if (/*$selectedPanel*/ ctx[1] === /*panel*/ ctx[2]) { + if (if_block) { + if_block.p(ctx, dirty); + + if (dirty & /*$selectedPanel*/ 2) { + transition_in(if_block, 1); + } + } else { + if_block = create_if_block$1(ctx); + if_block.c(); + transition_in(if_block, 1); + if_block.m(div, null); + } + } else if (if_block) { + group_outros(); + + transition_out(if_block, 1, 1, () => { + if_block = null; + }); + + check_outros(); + } + + if (!current || dirty & /*$labeledBy*/ 1 && div_aria_labelledby_value !== (div_aria_labelledby_value = /*$labeledBy*/ ctx[0][/*panel*/ ctx[2].id])) { + attr_dev(div, "aria-labelledby", div_aria_labelledby_value); + } + }, + i: function intro(local) { + if (current) return; + transition_in(if_block); + current = true; + }, + o: function outro(local) { + transition_out(if_block); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(div); + if (if_block) if_block.d(); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_fragment$4.name, + type: "component", + source: "", + ctx + }); + + return block; + } + + function instance$4($$self, $$props, $$invalidate) { + let $labeledBy; + let $selectedPanel; + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots('TabPanel', slots, ['default']); + const panel = { id: getId() }; + const { registerPanel, selectedPanel, labeledBy } = getContext(TABS); + validate_store(selectedPanel, 'selectedPanel'); + component_subscribe($$self, selectedPanel, value => $$invalidate(1, $selectedPanel = value)); + validate_store(labeledBy, 'labeledBy'); + component_subscribe($$self, labeledBy, value => $$invalidate(0, $labeledBy = value)); + registerPanel(panel); + const writable_props = []; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(` was created with unknown prop '${key}'`); + }); + + $$self.$$set = $$props => { + if ('$$scope' in $$props) $$invalidate(5, $$scope = $$props.$$scope); + }; + + $$self.$capture_state = () => ({ + getContext, + getId, + TABS, + panel, + registerPanel, + selectedPanel, + labeledBy, + $labeledBy, + $selectedPanel + }); + + return [$labeledBy, $selectedPanel, panel, selectedPanel, labeledBy, $$scope, slots]; + } + + class TabPanel extends SvelteComponentDev { + constructor(options) { + super(options); + init(this, options, instance$4, create_fragment$4, safe_not_equal, {}); + + dispatch_dev("SvelteRegisterComponent", { + component: this, + tagName: "TabPanel", + options, + id: create_fragment$4.name + }); + } + } + + function cubicOut(t) { + const f = t - 1.0; + return f * f * f + 1.0; + } + function quintOut(t) { + return --t * t * t * t * t + 1; + } + + function fade(node, { delay = 0, duration = 400, easing = identity } = {}) { + const o = +getComputedStyle(node).opacity; + return { + delay, + duration, + easing, + css: t => `opacity: ${t * o}` + }; + } + function fly(node, { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 } = {}) { + const style = getComputedStyle(node); + const target_opacity = +style.opacity; + const transform = style.transform === 'none' ? '' : style.transform; + const od = target_opacity * (1 - opacity); + const [xValue, xUnit] = split_css_unit(x); + const [yValue, yUnit] = split_css_unit(y); + return { + delay, + duration, + easing, + css: (t, u) => ` + transform: ${transform} translate(${(1 - t) * xValue}${xUnit}, ${(1 - t) * yValue}${yUnit}); + opacity: ${target_opacity - (od * u)}` + }; + } + + /* src/Match.svelte generated by Svelte v3.59.2 */ + + const file$3 = "src/Match.svelte"; + + function get_each_context$1(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[10] = list[i]; + child_ctx[13] = i; + const constants_0 = /*face*/ child_ctx[10].performers; + child_ctx[11] = constants_0; + return child_ctx; + } + + function get_each_context_1(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[14] = list[i]; + child_ctx[13] = i; + return child_ctx; + } + + function get_each_context_2(ctx, list, i) { + const child_ctx = ctx.slice(); + child_ctx[10] = list[i]; + child_ctx[13] = i; + return child_ctx; + } + + // (118:0) {#if visible} + function create_if_block(ctx) { + let div5; + let div4; + let div3; + let div0; + let tabs; + let t0; + let div2; + let div1; + let button0; + let t2; + let button1; + let div5_intro; + let div5_outro; + let current; + let mounted; + let dispose; + + tabs = new Tabs({ + props: { + $$slots: { default: [create_default_slot] }, + $$scope: { ctx } + }, + $$inline: true + }); + + const block = { + c: function create() { + div5 = element("div"); + div4 = element("div"); + div3 = element("div"); + div0 = element("div"); + create_component(tabs.$$.fragment); + t0 = space(); + div2 = element("div"); + div1 = element("div"); + button0 = element("button"); + button0.textContent = "Close"; + t2 = space(); + button1 = element("button"); + button1.textContent = "Toggle Visibility"; + attr_dev(div0, "class", "modal-body svelte-ssoxzi"); + add_location(div0, file$3, 130, 8, 3404); + attr_dev(button0, "id", "face_close"); + attr_dev(button0, "type", "button"); + attr_dev(button0, "class", "ml-2 btn btn-secondary svelte-ssoxzi"); + add_location(button0, file$3, 195, 12, 5952); + attr_dev(button1, "id", "face_toggle"); + attr_dev(button1, "type", "button"); + attr_dev(button1, "class", "ml-2 btn btn-secondary svelte-ssoxzi"); + add_location(button1, file$3, 201, 12, 6134); + attr_dev(div1, "class", "svelte-ssoxzi"); + add_location(div1, file$3, 194, 10, 5934); + attr_dev(div2, "class", "ModalFooter modal-footer svelte-ssoxzi"); + add_location(div2, file$3, 193, 8, 5885); + attr_dev(div3, "class", "modal-content svelte-ssoxzi"); + add_location(div3, file$3, 129, 6, 3368); + attr_dev(div4, "class", "modal-dialog modal-xl svelte-ssoxzi"); + add_location(div4, file$3, 128, 4, 3326); + attr_dev(div5, "role", "dialog"); + attr_dev(div5, "aria-modal", "true"); + attr_dev(div5, "class", "fade ModalComponent modal show svelte-ssoxzi"); + attr_dev(div5, "tabindex", "-1"); + set_style(div5, "display", "block"); + add_location(div5, file$3, 118, 2, 3083); + }, + m: function mount(target, anchor) { + insert_dev(target, div5, anchor); + append_dev(div5, div4); + append_dev(div4, div3); + append_dev(div3, div0); + mount_component(tabs, div0, null); + append_dev(div3, t0); + append_dev(div3, div2); + append_dev(div2, div1); + append_dev(div1, button0); + append_dev(div1, t2); + append_dev(div1, button1); + /*div5_binding*/ ctx[8](div5); + current = true; + + if (!mounted) { + dispose = [ + listen_dev(button0, "click", /*close*/ ctx[3], false, false, false, false), + listen_dev(button1, "click", /*toggle*/ ctx[4], false, false, false, false) + ]; + + mounted = true; + } + }, + p: function update(ctx, dirty) { + const tabs_changes = {}; + + if (dirty & /*$$scope, matches*/ 131073) { + tabs_changes.$$scope = { dirty, ctx }; + } + + tabs.$set(tabs_changes); + }, + i: function intro(local) { + if (current) return; + transition_in(tabs.$$.fragment, local); + + add_render_callback(() => { + if (!current) return; + if (div5_outro) div5_outro.end(1); + div5_intro = create_in_transition(div5, fly, { y: 100, duration: 400 }); + div5_intro.start(); + }); + + current = true; + }, + o: function outro(local) { + transition_out(tabs.$$.fragment, local); + if (div5_intro) div5_intro.invalidate(); + div5_outro = create_out_transition(div5, fly, { y: -100, duration: 400 }); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(div5); + destroy_component(tabs); + /*div5_binding*/ ctx[8](null); + if (detaching && div5_outro) div5_outro.end(); + mounted = false; + run_all(dispose); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_if_block.name, + type: "if", + source: "(118:0) {#if visible}", + ctx + }); + + return block; + } + + // (135:16) + function create_default_slot_3(ctx) { + let img; + let img_src_value; + let t; + + const block = { + c: function create() { + img = element("img"); + t = space(); + if (!src_url_equal(img.src, img_src_value = "data:image/jpg;base64," + /*face*/ ctx[10].image)) attr_dev(img, "src", img_src_value); + attr_dev(img, "alt", ""); + attr_dev(img, "class", "face-tab svelte-ssoxzi"); + add_location(img, file$3, 135, 18, 3562); + }, + m: function mount(target, anchor) { + insert_dev(target, img, anchor); + insert_dev(target, t, anchor); + }, + p: function update(ctx, dirty) { + if (dirty & /*matches*/ 1 && !src_url_equal(img.src, img_src_value = "data:image/jpg;base64," + /*face*/ ctx[10].image)) { + attr_dev(img, "src", img_src_value); + } + }, + d: function destroy(detaching) { + if (detaching) detach_dev(img); + if (detaching) detach_dev(t); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_default_slot_3.name, + type: "slot", + source: "(135:16) ", + ctx + }); + + return block; + } + + // (134:14) {#each matches as face, i (face.image)} + function create_each_block_2(key_1, ctx) { + let first; + let tab; + let current; + + tab = new Tab({ + props: { + $$slots: { default: [create_default_slot_3] }, + $$scope: { ctx } + }, + $$inline: true + }); + + const block = { + key: key_1, + first: null, + c: function create() { + first = empty(); + create_component(tab.$$.fragment); + this.first = first; + }, + m: function mount(target, anchor) { + insert_dev(target, first, anchor); + mount_component(tab, target, anchor); + current = true; + }, + p: function update(new_ctx, dirty) { + ctx = new_ctx; + const tab_changes = {}; + + if (dirty & /*$$scope, matches*/ 131073) { + tab_changes.$$scope = { dirty, ctx }; + } + + tab.$set(tab_changes); + }, + i: function intro(local) { + if (current) return; + transition_in(tab.$$.fragment, local); + current = true; + }, + o: function outro(local) { + transition_out(tab.$$.fragment, local); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(first); + destroy_component(tab, detaching); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_each_block_2.name, + type: "each", + source: "(134:14) {#each matches as face, i (face.image)}", + ctx + }); + + return block; + } + + // (133:12) + function create_default_slot_2(ctx) { + let each_blocks = []; + let each_1_lookup = new Map(); + let each_1_anchor; + let current; + let each_value_2 = /*matches*/ ctx[0]; + validate_each_argument(each_value_2); + const get_key = ctx => /*face*/ ctx[10].image; + validate_each_keys(ctx, each_value_2, get_each_context_2, get_key); + + for (let i = 0; i < each_value_2.length; i += 1) { + let child_ctx = get_each_context_2(ctx, each_value_2, i); + let key = get_key(child_ctx); + each_1_lookup.set(key, each_blocks[i] = create_each_block_2(key, child_ctx)); + } + + const block = { + c: function create() { + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].c(); + } + + each_1_anchor = empty(); + }, + m: function mount(target, anchor) { + for (let i = 0; i < each_blocks.length; i += 1) { + if (each_blocks[i]) { + each_blocks[i].m(target, anchor); + } + } + + insert_dev(target, each_1_anchor, anchor); + current = true; + }, + p: function update(ctx, dirty) { + if (dirty & /*matches*/ 1) { + each_value_2 = /*matches*/ ctx[0]; + validate_each_argument(each_value_2); + group_outros(); + validate_each_keys(ctx, each_value_2, get_each_context_2, get_key); + each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value_2, each_1_lookup, each_1_anchor.parentNode, outro_and_destroy_block, create_each_block_2, each_1_anchor, get_each_context_2); + check_outros(); + } + }, + i: function intro(local) { + if (current) return; + + for (let i = 0; i < each_value_2.length; i += 1) { + transition_in(each_blocks[i]); + } + + current = true; + }, + o: function outro(local) { + for (let i = 0; i < each_blocks.length; i += 1) { + transition_out(each_blocks[i]); + } + + current = false; + }, + d: function destroy(detaching) { + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].d(detaching); + } + + if (detaching) detach_dev(each_1_anchor); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_default_slot_2.name, + type: "slot", + source: "(133:12) ", + ctx + }); + + return block; + } + + // (150:20) {#each performers as match, i (match.id)} + function create_each_block_1(key_1, ctx) { + let div4; + let div0; + let img; + let img_alt_value; + let img_src_value; + let t0; + let span0; + let span0_class_value; + let t1; + let div3; + let h5; + let div2; + let a; + let t2_value = /*match*/ ctx[14].name + ""; + let t2; + let a_href_value; + let t3; + let span1; + let div1; + let t4_value = /*match*/ ctx[14].confidence + ""; + let t4; + let t5; + let t6; + let div4_id_value; + let mounted; + let dispose; + + function click_handler(...args) { + return /*click_handler*/ ctx[6](/*match*/ ctx[14], ...args); + } + + function keypress_handler(...args) { + return /*keypress_handler*/ ctx[7](/*match*/ ctx[14], ...args); + } + + const block = { + key: key_1, + first: null, + c: function create() { + div4 = element("div"); + div0 = element("div"); + img = element("img"); + t0 = space(); + span0 = element("span"); + t1 = space(); + div3 = element("div"); + h5 = element("h5"); + div2 = element("div"); + a = element("a"); + t2 = text(t2_value); + t3 = space(); + span1 = element("span"); + div1 = element("div"); + t4 = text(t4_value); + t5 = text("%"); + t6 = space(); + attr_dev(img, "class", "performer-card-image svelte-ssoxzi"); + attr_dev(img, "alt", img_alt_value = /*match*/ ctx[14].name); + if (!src_url_equal(img.src, img_src_value = /*match*/ ctx[14].image)) attr_dev(img, "src", img_src_value); + add_location(img, file$3, 160, 26, 4544); + attr_dev(span0, "class", span0_class_value = "performer-card__country-flag fi fi-" + /*match*/ ctx[14].country?.toLowerCase() + " svelte-ssoxzi"); + add_location(span0, file$3, 166, 26, 4795); + attr_dev(div0, "class", "thumbnail-section svelte-ssoxzi"); + add_location(div0, file$3, 159, 24, 4486); + attr_dev(a, "href", a_href_value = "https://stashdb.org/performers/" + /*match*/ ctx[14].id); + attr_dev(a, "target", "_blank"); + attr_dev(a, "class", "svelte-ssoxzi"); + add_location(a, file$3, 173, 30, 5179); + attr_dev(div1, "class", "svelte-ssoxzi"); + add_location(div1, file$3, 180, 33, 5526); + attr_dev(span1, "class", "tag-item badge badge-pill svelte-ssoxzi"); + add_location(span1, file$3, 179, 30, 5453); + set_style(div2, "-webkit-line-clamp", "2"); + attr_dev(div2, "class", "svelte-ssoxzi"); + add_location(div2, file$3, 172, 28, 5113); + attr_dev(h5, "class", "card-section-title flex-aligned svelte-ssoxzi"); + add_location(h5, file$3, 171, 26, 5040); + attr_dev(div3, "class", "card-section svelte-ssoxzi"); + add_location(div3, file$3, 170, 24, 4987); + attr_dev(div4, "draggable", "false"); + attr_dev(div4, "class", "performer-card grid-card card svelte-ssoxzi"); + attr_dev(div4, "id", div4_id_value = "face-" + /*i*/ ctx[13]); + add_location(div4, file$3, 150, 22, 4070); + this.first = div4; + }, + m: function mount(target, anchor) { + insert_dev(target, div4, anchor); + append_dev(div4, div0); + append_dev(div0, img); + append_dev(div0, t0); + append_dev(div0, span0); + append_dev(div4, t1); + append_dev(div4, div3); + append_dev(div3, h5); + append_dev(h5, div2); + append_dev(div2, a); + append_dev(a, t2); + append_dev(div2, t3); + append_dev(div2, span1); + append_dev(span1, div1); + append_dev(div1, t4); + append_dev(div1, t5); + append_dev(div4, t6); + + if (!mounted) { + dispose = [ + action_destroyer(smoothload.call(null, img)), + listen_dev(div4, "click", click_handler, false, false, false, false), + listen_dev(div4, "keypress", keypress_handler, false, false, false, false) + ]; + + mounted = true; + } + }, + p: function update(new_ctx, dirty) { + ctx = new_ctx; + + if (dirty & /*matches*/ 1 && img_alt_value !== (img_alt_value = /*match*/ ctx[14].name)) { + attr_dev(img, "alt", img_alt_value); + } + + if (dirty & /*matches*/ 1 && !src_url_equal(img.src, img_src_value = /*match*/ ctx[14].image)) { + attr_dev(img, "src", img_src_value); + } + + if (dirty & /*matches*/ 1 && span0_class_value !== (span0_class_value = "performer-card__country-flag fi fi-" + /*match*/ ctx[14].country?.toLowerCase() + " svelte-ssoxzi")) { + attr_dev(span0, "class", span0_class_value); + } + + if (dirty & /*matches*/ 1 && t2_value !== (t2_value = /*match*/ ctx[14].name + "")) set_data_dev(t2, t2_value); + + if (dirty & /*matches*/ 1 && a_href_value !== (a_href_value = "https://stashdb.org/performers/" + /*match*/ ctx[14].id)) { + attr_dev(a, "href", a_href_value); + } + + if (dirty & /*matches*/ 1 && t4_value !== (t4_value = /*match*/ ctx[14].confidence + "")) set_data_dev(t4, t4_value); + + if (dirty & /*matches*/ 1 && div4_id_value !== (div4_id_value = "face-" + /*i*/ ctx[13])) { + attr_dev(div4, "id", div4_id_value); + } + }, + d: function destroy(detaching) { + if (detaching) detach_dev(div4); + mounted = false; + run_all(dispose); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_each_block_1.name, + type: "each", + source: "(150:20) {#each performers as match, i (match.id)}", + ctx + }); + + return block; + } + + // (147:14) + function create_default_slot_1(ctx) { + let div1; + let div0; + let each_blocks = []; + let each_1_lookup = new Map(); + let t; + let each_value_1 = /*performers*/ ctx[11]; + validate_each_argument(each_value_1); + const get_key = ctx => /*match*/ ctx[14].id; + validate_each_keys(ctx, each_value_1, get_each_context_1, get_key); + + for (let i = 0; i < each_value_1.length; i += 1) { + let child_ctx = get_each_context_1(ctx, each_value_1, i); + let key = get_key(child_ctx); + each_1_lookup.set(key, each_blocks[i] = create_each_block_1(key, child_ctx)); + } + + const block = { + c: function create() { + div1 = element("div"); + div0 = element("div"); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].c(); + } + + t = space(); + attr_dev(div0, "class", "carousel svelte-ssoxzi"); + add_location(div0, file$3, 148, 18, 3963); + attr_dev(div1, "class", "row svelte-ssoxzi"); + add_location(div1, file$3, 147, 16, 3927); + }, + m: function mount(target, anchor) { + insert_dev(target, div1, anchor); + append_dev(div1, div0); + + for (let i = 0; i < each_blocks.length; i += 1) { + if (each_blocks[i]) { + each_blocks[i].m(div0, null); + } + } + + insert_dev(target, t, anchor); + }, + p: function update(ctx, dirty) { + if (dirty & /*matches, addPerformer*/ 33) { + each_value_1 = /*performers*/ ctx[11]; + validate_each_argument(each_value_1); + validate_each_keys(ctx, each_value_1, get_each_context_1, get_key); + each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value_1, each_1_lookup, div0, destroy_block, create_each_block_1, null, get_each_context_1); + } + }, + d: function destroy(detaching) { + if (detaching) detach_dev(div1); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].d(); + } + + if (detaching) detach_dev(t); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_default_slot_1.name, + type: "slot", + source: "(147:14) ", + ctx + }); + + return block; + } + + // (145:12) {#each matches as face, i (face.image)} + function create_each_block$1(key_1, ctx) { + let first; + let tabpanel; + let current; + + tabpanel = new TabPanel({ + props: { + $$slots: { default: [create_default_slot_1] }, + $$scope: { ctx } + }, + $$inline: true + }); + + const block = { + key: key_1, + first: null, + c: function create() { + first = empty(); + create_component(tabpanel.$$.fragment); + this.first = first; + }, + m: function mount(target, anchor) { + insert_dev(target, first, anchor); + mount_component(tabpanel, target, anchor); + current = true; + }, + p: function update(new_ctx, dirty) { + ctx = new_ctx; + const tabpanel_changes = {}; + + if (dirty & /*$$scope, matches*/ 131073) { + tabpanel_changes.$$scope = { dirty, ctx }; + } + + tabpanel.$set(tabpanel_changes); + }, + i: function intro(local) { + if (current) return; + transition_in(tabpanel.$$.fragment, local); + current = true; + }, + o: function outro(local) { + transition_out(tabpanel.$$.fragment, local); + current = false; + }, + d: function destroy(detaching) { + if (detaching) detach_dev(first); + destroy_component(tabpanel, detaching); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_each_block$1.name, + type: "each", + source: "(145:12) {#each matches as face, i (face.image)}", + ctx + }); + + return block; + } + + // (132:10) + function create_default_slot(ctx) { + let tablist; + let t; + let each_blocks = []; + let each_1_lookup = new Map(); + let each_1_anchor; + let current; + + tablist = new TabList({ + props: { + $$slots: { default: [create_default_slot_2] }, + $$scope: { ctx } + }, + $$inline: true + }); + + let each_value = /*matches*/ ctx[0]; + validate_each_argument(each_value); + const get_key = ctx => /*face*/ ctx[10].image; + validate_each_keys(ctx, each_value, get_each_context$1, get_key); + + for (let i = 0; i < each_value.length; i += 1) { + let child_ctx = get_each_context$1(ctx, each_value, i); + let key = get_key(child_ctx); + each_1_lookup.set(key, each_blocks[i] = create_each_block$1(key, child_ctx)); + } + + const block = { + c: function create() { + create_component(tablist.$$.fragment); + t = space(); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].c(); + } + + each_1_anchor = empty(); + }, + m: function mount(target, anchor) { + mount_component(tablist, target, anchor); + insert_dev(target, t, anchor); + + for (let i = 0; i < each_blocks.length; i += 1) { + if (each_blocks[i]) { + each_blocks[i].m(target, anchor); + } + } + + insert_dev(target, each_1_anchor, anchor); + current = true; + }, + p: function update(ctx, dirty) { + const tablist_changes = {}; + + if (dirty & /*$$scope, matches*/ 131073) { + tablist_changes.$$scope = { dirty, ctx }; + } + + tablist.$set(tablist_changes); + + if (dirty & /*matches, addPerformer*/ 33) { + each_value = /*matches*/ ctx[0]; + validate_each_argument(each_value); + group_outros(); + validate_each_keys(ctx, each_value, get_each_context$1, get_key); + each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, outro_and_destroy_block, create_each_block$1, each_1_anchor, get_each_context$1); + check_outros(); + } + }, + i: function intro(local) { + if (current) return; + transition_in(tablist.$$.fragment, local); + + for (let i = 0; i < each_value.length; i += 1) { + transition_in(each_blocks[i]); + } + + current = true; + }, + o: function outro(local) { + transition_out(tablist.$$.fragment, local); + + for (let i = 0; i < each_blocks.length; i += 1) { + transition_out(each_blocks[i]); + } + + current = false; + }, + d: function destroy(detaching) { + destroy_component(tablist, detaching); + if (detaching) detach_dev(t); + + for (let i = 0; i < each_blocks.length; i += 1) { + each_blocks[i].d(detaching); + } + + if (detaching) detach_dev(each_1_anchor); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_default_slot.name, + type: "slot", + source: "(132:10) ", + ctx + }); + + return block; + } + + function create_fragment$3(ctx) { + let if_block_anchor; + let current; + let if_block = /*visible*/ ctx[1] && create_if_block(ctx); + + const block = { + c: function create() { + if (if_block) if_block.c(); + if_block_anchor = empty(); + }, + l: function claim(nodes) { + throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option"); + }, + m: function mount(target, anchor) { + if (if_block) if_block.m(target, anchor); + insert_dev(target, if_block_anchor, anchor); + current = true; + }, + p: function update(ctx, [dirty]) { + if (/*visible*/ ctx[1]) { + if (if_block) { + if_block.p(ctx, dirty); + + if (dirty & /*visible*/ 2) { + transition_in(if_block, 1); + } + } else { + if_block = create_if_block(ctx); + if_block.c(); + transition_in(if_block, 1); + if_block.m(if_block_anchor.parentNode, if_block_anchor); + } + } else if (if_block) { + group_outros(); + + transition_out(if_block, 1, 1, () => { + if_block = null; + }); + + check_outros(); + } + }, + i: function intro(local) { + if (current) return; + transition_in(if_block); + current = true; + }, + o: function outro(local) { + transition_out(if_block); + current = false; + }, + d: function destroy(detaching) { + if (if_block) if_block.d(detaching); + if (detaching) detach_dev(if_block_anchor); + } + }; + + dispatch_dev("SvelteRegisterBlock", { + block, + id: create_fragment$3.name, + type: "component", + source: "", + ctx + }); + + return block; + } + + function instance$3($$self, $$props, $$invalidate) { + let { $$slots: slots = {}, $$scope } = $$props; + validate_slots('Match', slots, []); + let { matches = [] } = $$props; + let matchesSelected = 0; + let visible = false; + let modal; + + onMount(() => { + $$invalidate(1, visible = true); + }); + + async function close() { + $$invalidate(1, visible = false); + + setTimeout( + () => { + modal.remove(); + }, + 400 + ); + } + + function toggle() { + $$invalidate(2, modal.style.opacity = modal.style.opacity == "0.1" ? "1.0" : "0.1", modal); + } + + async function addPerformer(current, id_) { + var performers = await getPerformers(id_); + current.classList.add("assigned"); + + // if the users doesn't have a performer with the same stash id, get the data from stash box and create a new performer + if (performers.length === 0) { + var performer = await getPerformerDataFromStashID(id_); + + if (performer === undefined) { + current.classList.remove("assigned"); + alert("Could not retrieve performer data from stash box"); + return; + } + + performer.image = performer.images[0]; + var endpoint = await getStashboxEndpoint(); + + // delete some fields that are not needed and will not be accepted by local stash instance + delete performer.images; + + delete performer.remote_site_id; + + if (performer.height) { + performer.height_cm = performer.height; + delete performer.height; + } + + if (performer.aliases) { + performer.alias_list = performer.aliases; + delete performer.aliases; + } + + performer.stash_ids = [{ endpoint, stash_id: id_ }]; + id_ = await createPerformer(performer); + + if ("errors" in id_) { + current.classList.remove("assigned"); + alert("Error while creating performer:" + id_.errors[0].message); + return; + } + + id_ = id_.data.performerCreate.id; + } else { + id_ = performers[0].id; + } + + let [scenario, scenarioId] = getScenarioAndID(); + var performIds; + + if (scenario === "scenes") { + performIds = await getPerformersForScene(scenarioId); + + if (performIds.includes(id_)) { + current.classList.remove("assigned"); + alert("Performer already assigned to scene"); + return; + } + + performIds.push(id_); + await updateScene(scenarioId, performIds); + } else if (scenario === "images") { + performIds = await getPerformersForImage(scenarioId); + + if (performIds.includes(id_)) { + current.classList.remove("assigned"); + alert("Performer already assigned to image"); + return; + } + + performIds.push(id_); + await updateImage(scenarioId, performIds); + } + + matchesSelected += 1; + + if (matchesSelected === matches.length) { + close(); + window.location.reload(); + } + } + + const writable_props = ['matches']; + + Object.keys($$props).forEach(key => { + if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') console.warn(` was created with unknown prop '${key}'`); + }); + + const click_handler = (match, event) => addPerformer(event.target, match.id); + const keypress_handler = (match, event) => addPerformer(event.target, match.id); + + function div5_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](() => { + modal = $$value; + $$invalidate(2, modal); + }); + } + + $$self.$$set = $$props => { + if ('matches' in $$props) $$invalidate(0, matches = $$props.matches); + }; + + $$self.$capture_state = () => ({ + onMount, + Tab, + TabList, + TabPanel, + Tabs, + fly, + createPerformer, + getPerformerDataFromStashID, + getPerformers, + getPerformersForImage, + getPerformersForScene, + getScenarioAndID, + getStashboxEndpoint, + smoothload, + updateImage, + updateScene, + matches, + matchesSelected, + visible, + modal, + close, + toggle, + addPerformer + }); + + $$self.$inject_state = $$props => { + if ('matches' in $$props) $$invalidate(0, matches = $$props.matches); + if ('matchesSelected' in $$props) matchesSelected = $$props.matchesSelected; + if ('visible' in $$props) $$invalidate(1, visible = $$props.visible); + if ('modal' in $$props) $$invalidate(2, modal = $$props.modal); + }; + + if ($$props && "$$inject" in $$props) { + $$self.$inject_state($$props.$$inject); + } + + return [ + matches, + visible, + modal, + close, + toggle, + addPerformer, + click_handler, + keypress_handler, + div5_binding + ]; + } + + class Match extends SvelteComponentDev { + constructor(options) { + super(options); + init(this, options, instance$3, create_fragment$3, safe_not_equal, { matches: 0 }); + + dispatch_dev("SvelteRegisterComponent", { + component: this, + tagName: "Match", + options, + id: create_fragment$3.name + }); + } + + get matches() { + throw new Error(": Props cannot be read directly from the component instance unless compiling with 'accessors: true' or ''"); + } + + set matches(value) { + throw new Error(": Props cannot be set directly on the component instance unless compiling with 'accessors: true' or ''"); + } + } + + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + function createCommonjsModule(fn) { + var module = { exports: {} }; + return fn(module, module.exports), module.exports; + } + + /*! + * html2canvas 1.4.1 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + + var html2canvas = createCommonjsModule(function (module, exports) { + (function (global, factory) { + module.exports = factory() ; + }(commonjsGlobal, (function () { + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + } + + function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + } + + function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || from); + } + + var Bounds = /** @class */ (function () { + function Bounds(left, top, width, height) { + this.left = left; + this.top = top; + this.width = width; + this.height = height; + } + Bounds.prototype.add = function (x, y, w, h) { + return new Bounds(this.left + x, this.top + y, this.width + w, this.height + h); + }; + Bounds.fromClientRect = function (context, clientRect) { + return new Bounds(clientRect.left + context.windowBounds.left, clientRect.top + context.windowBounds.top, clientRect.width, clientRect.height); + }; + Bounds.fromDOMRectList = function (context, domRectList) { + var domRect = Array.from(domRectList).find(function (rect) { return rect.width !== 0; }); + return domRect + ? new Bounds(domRect.left + context.windowBounds.left, domRect.top + context.windowBounds.top, domRect.width, domRect.height) + : Bounds.EMPTY; + }; + Bounds.EMPTY = new Bounds(0, 0, 0, 0); + return Bounds; + }()); + var parseBounds = function (context, node) { + return Bounds.fromClientRect(context, node.getBoundingClientRect()); + }; + var parseDocumentSize = function (document) { + var body = document.body; + var documentElement = document.documentElement; + if (!body || !documentElement) { + throw new Error("Unable to get document size"); + } + var width = Math.max(Math.max(body.scrollWidth, documentElement.scrollWidth), Math.max(body.offsetWidth, documentElement.offsetWidth), Math.max(body.clientWidth, documentElement.clientWidth)); + var height = Math.max(Math.max(body.scrollHeight, documentElement.scrollHeight), Math.max(body.offsetHeight, documentElement.offsetHeight), Math.max(body.clientHeight, documentElement.clientHeight)); + return new Bounds(0, 0, width, height); + }; + + /* + * css-line-break 2.1.0 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var toCodePoints$1 = function (str) { + var codePoints = []; + var i = 0; + var length = str.length; + while (i < length) { + var value = str.charCodeAt(i++); + if (value >= 0xd800 && value <= 0xdbff && i < length) { + var extra = str.charCodeAt(i++); + if ((extra & 0xfc00) === 0xdc00) { + codePoints.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000); + } + else { + codePoints.push(value); + i--; + } + } + else { + codePoints.push(value); + } + } + return codePoints; + }; + var fromCodePoint$1 = function () { + var codePoints = []; + for (var _i = 0; _i < arguments.length; _i++) { + codePoints[_i] = arguments[_i]; + } + if (String.fromCodePoint) { + return String.fromCodePoint.apply(String, codePoints); + } + var length = codePoints.length; + if (!length) { + return ''; + } + var codeUnits = []; + var index = -1; + var result = ''; + while (++index < length) { + var codePoint = codePoints[index]; + if (codePoint <= 0xffff) { + codeUnits.push(codePoint); + } + else { + codePoint -= 0x10000; + codeUnits.push((codePoint >> 10) + 0xd800, (codePoint % 0x400) + 0xdc00); + } + if (index + 1 === length || codeUnits.length > 0x4000) { + result += String.fromCharCode.apply(String, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + var chars$2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Use a lookup table to find the index. + var lookup$2 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (var i$2 = 0; i$2 < chars$2.length; i$2++) { + lookup$2[chars$2.charCodeAt(i$2)] = i$2; + } + + /* + * utrie 1.0.2 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var chars$1$1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Use a lookup table to find the index. + var lookup$1$1 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (var i$1$1 = 0; i$1$1 < chars$1$1.length; i$1$1++) { + lookup$1$1[chars$1$1.charCodeAt(i$1$1)] = i$1$1; + } + var decode$1 = function (base64) { + var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; + if (base64[base64.length - 1] === '=') { + bufferLength--; + if (base64[base64.length - 2] === '=') { + bufferLength--; + } + } + var buffer = typeof ArrayBuffer !== 'undefined' && + typeof Uint8Array !== 'undefined' && + typeof Uint8Array.prototype.slice !== 'undefined' + ? new ArrayBuffer(bufferLength) + : new Array(bufferLength); + var bytes = Array.isArray(buffer) ? buffer : new Uint8Array(buffer); + for (i = 0; i < len; i += 4) { + encoded1 = lookup$1$1[base64.charCodeAt(i)]; + encoded2 = lookup$1$1[base64.charCodeAt(i + 1)]; + encoded3 = lookup$1$1[base64.charCodeAt(i + 2)]; + encoded4 = lookup$1$1[base64.charCodeAt(i + 3)]; + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + return buffer; + }; + var polyUint16Array$1 = function (buffer) { + var length = buffer.length; + var bytes = []; + for (var i = 0; i < length; i += 2) { + bytes.push((buffer[i + 1] << 8) | buffer[i]); + } + return bytes; + }; + var polyUint32Array$1 = function (buffer) { + var length = buffer.length; + var bytes = []; + for (var i = 0; i < length; i += 4) { + bytes.push((buffer[i + 3] << 24) | (buffer[i + 2] << 16) | (buffer[i + 1] << 8) | buffer[i]); + } + return bytes; + }; + + /** Shift size for getting the index-2 table offset. */ + var UTRIE2_SHIFT_2$1 = 5; + /** Shift size for getting the index-1 table offset. */ + var UTRIE2_SHIFT_1$1 = 6 + 5; + /** + * Shift size for shifting left the index array values. + * Increases possible data size with 16-bit index values at the cost + * of compactability. + * This requires data blocks to be aligned by UTRIE2_DATA_GRANULARITY. + */ + var UTRIE2_INDEX_SHIFT$1 = 2; + /** + * Difference between the two shift sizes, + * for getting an index-1 offset from an index-2 offset. 6=11-5 + */ + var UTRIE2_SHIFT_1_2$1 = UTRIE2_SHIFT_1$1 - UTRIE2_SHIFT_2$1; + /** + * The part of the index-2 table for U+D800..U+DBFF stores values for + * lead surrogate code _units_ not code _points_. + * Values for lead surrogate code _points_ are indexed with this portion of the table. + * Length=32=0x20=0x400>>UTRIE2_SHIFT_2. (There are 1024=0x400 lead surrogates.) + */ + var UTRIE2_LSCP_INDEX_2_OFFSET$1 = 0x10000 >> UTRIE2_SHIFT_2$1; + /** Number of entries in a data block. 32=0x20 */ + var UTRIE2_DATA_BLOCK_LENGTH$1 = 1 << UTRIE2_SHIFT_2$1; + /** Mask for getting the lower bits for the in-data-block offset. */ + var UTRIE2_DATA_MASK$1 = UTRIE2_DATA_BLOCK_LENGTH$1 - 1; + var UTRIE2_LSCP_INDEX_2_LENGTH$1 = 0x400 >> UTRIE2_SHIFT_2$1; + /** Count the lengths of both BMP pieces. 2080=0x820 */ + var UTRIE2_INDEX_2_BMP_LENGTH$1 = UTRIE2_LSCP_INDEX_2_OFFSET$1 + UTRIE2_LSCP_INDEX_2_LENGTH$1; + /** + * The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820. + * Length 32=0x20 for lead bytes C0..DF, regardless of UTRIE2_SHIFT_2. + */ + var UTRIE2_UTF8_2B_INDEX_2_OFFSET$1 = UTRIE2_INDEX_2_BMP_LENGTH$1; + var UTRIE2_UTF8_2B_INDEX_2_LENGTH$1 = 0x800 >> 6; /* U+0800 is the first code point after 2-byte UTF-8 */ + /** + * The index-1 table, only used for supplementary code points, at offset 2112=0x840. + * Variable length, for code points up to highStart, where the last single-value range starts. + * Maximum length 512=0x200=0x100000>>UTRIE2_SHIFT_1. + * (For 0x100000 supplementary code points U+10000..U+10ffff.) + * + * The part of the index-2 table for supplementary code points starts + * after this index-1 table. + * + * Both the index-1 table and the following part of the index-2 table + * are omitted completely if there is only BMP data. + */ + var UTRIE2_INDEX_1_OFFSET$1 = UTRIE2_UTF8_2B_INDEX_2_OFFSET$1 + UTRIE2_UTF8_2B_INDEX_2_LENGTH$1; + /** + * Number of index-1 entries for the BMP. 32=0x20 + * This part of the index-1 table is omitted from the serialized form. + */ + var UTRIE2_OMITTED_BMP_INDEX_1_LENGTH$1 = 0x10000 >> UTRIE2_SHIFT_1$1; + /** Number of entries in an index-2 block. 64=0x40 */ + var UTRIE2_INDEX_2_BLOCK_LENGTH$1 = 1 << UTRIE2_SHIFT_1_2$1; + /** Mask for getting the lower bits for the in-index-2-block offset. */ + var UTRIE2_INDEX_2_MASK$1 = UTRIE2_INDEX_2_BLOCK_LENGTH$1 - 1; + var slice16$1 = function (view, start, end) { + if (view.slice) { + return view.slice(start, end); + } + return new Uint16Array(Array.prototype.slice.call(view, start, end)); + }; + var slice32$1 = function (view, start, end) { + if (view.slice) { + return view.slice(start, end); + } + return new Uint32Array(Array.prototype.slice.call(view, start, end)); + }; + var createTrieFromBase64$1 = function (base64, _byteLength) { + var buffer = decode$1(base64); + var view32 = Array.isArray(buffer) ? polyUint32Array$1(buffer) : new Uint32Array(buffer); + var view16 = Array.isArray(buffer) ? polyUint16Array$1(buffer) : new Uint16Array(buffer); + var headerLength = 24; + var index = slice16$1(view16, headerLength / 2, view32[4] / 2); + var data = view32[5] === 2 + ? slice16$1(view16, (headerLength + view32[4]) / 2) + : slice32$1(view32, Math.ceil((headerLength + view32[4]) / 4)); + return new Trie$1(view32[0], view32[1], view32[2], view32[3], index, data); + }; + var Trie$1 = /** @class */ (function () { + function Trie(initialValue, errorValue, highStart, highValueIndex, index, data) { + this.initialValue = initialValue; + this.errorValue = errorValue; + this.highStart = highStart; + this.highValueIndex = highValueIndex; + this.index = index; + this.data = data; + } + /** + * Get the value for a code point as stored in the Trie. + * + * @param codePoint the code point + * @return the value + */ + Trie.prototype.get = function (codePoint) { + var ix; + if (codePoint >= 0) { + if (codePoint < 0x0d800 || (codePoint > 0x0dbff && codePoint <= 0x0ffff)) { + // Ordinary BMP code point, excluding leading surrogates. + // BMP uses a single level lookup. BMP index starts at offset 0 in the Trie2 index. + // 16 bit data is stored in the index array itself. + ix = this.index[codePoint >> UTRIE2_SHIFT_2$1]; + ix = (ix << UTRIE2_INDEX_SHIFT$1) + (codePoint & UTRIE2_DATA_MASK$1); + return this.data[ix]; + } + if (codePoint <= 0xffff) { + // Lead Surrogate Code Point. A Separate index section is stored for + // lead surrogate code units and code points. + // The main index has the code unit data. + // For this function, we need the code point data. + // Note: this expression could be refactored for slightly improved efficiency, but + // surrogate code points will be so rare in practice that it's not worth it. + ix = this.index[UTRIE2_LSCP_INDEX_2_OFFSET$1 + ((codePoint - 0xd800) >> UTRIE2_SHIFT_2$1)]; + ix = (ix << UTRIE2_INDEX_SHIFT$1) + (codePoint & UTRIE2_DATA_MASK$1); + return this.data[ix]; + } + if (codePoint < this.highStart) { + // Supplemental code point, use two-level lookup. + ix = UTRIE2_INDEX_1_OFFSET$1 - UTRIE2_OMITTED_BMP_INDEX_1_LENGTH$1 + (codePoint >> UTRIE2_SHIFT_1$1); + ix = this.index[ix]; + ix += (codePoint >> UTRIE2_SHIFT_2$1) & UTRIE2_INDEX_2_MASK$1; + ix = this.index[ix]; + ix = (ix << UTRIE2_INDEX_SHIFT$1) + (codePoint & UTRIE2_DATA_MASK$1); + return this.data[ix]; + } + if (codePoint <= 0x10ffff) { + return this.data[this.highValueIndex]; + } + } + // Fall through. The code point is outside of the legal range of 0..0x10ffff. + return this.errorValue; + }; + return Trie; + }()); + + /* + * base64-arraybuffer 1.0.2 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var chars$3 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Use a lookup table to find the index. + var lookup$3 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (var i$3 = 0; i$3 < chars$3.length; i$3++) { + lookup$3[chars$3.charCodeAt(i$3)] = i$3; + } + + var base64$1 = 'KwAAAAAAAAAACA4AUD0AADAgAAACAAAAAAAIABAAGABAAEgAUABYAGAAaABgAGgAYgBqAF8AZwBgAGgAcQB5AHUAfQCFAI0AlQCdAKIAqgCyALoAYABoAGAAaABgAGgAwgDKAGAAaADGAM4A0wDbAOEA6QDxAPkAAQEJAQ8BFwF1AH0AHAEkASwBNAE6AUIBQQFJAVEBWQFhAWgBcAF4ATAAgAGGAY4BlQGXAZ8BpwGvAbUBvQHFAc0B0wHbAeMB6wHxAfkBAQIJAvEBEQIZAiECKQIxAjgCQAJGAk4CVgJeAmQCbAJ0AnwCgQKJApECmQKgAqgCsAK4ArwCxAIwAMwC0wLbAjAA4wLrAvMC+AIAAwcDDwMwABcDHQMlAy0DNQN1AD0DQQNJA0kDSQNRA1EDVwNZA1kDdQB1AGEDdQBpA20DdQN1AHsDdQCBA4kDkQN1AHUAmQOhA3UAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AKYDrgN1AHUAtgO+A8YDzgPWAxcD3gPjA+sD8wN1AHUA+wMDBAkEdQANBBUEHQQlBCoEFwMyBDgEYABABBcDSARQBFgEYARoBDAAcAQzAXgEgASIBJAEdQCXBHUAnwSnBK4EtgS6BMIEyAR1AHUAdQB1AHUAdQCVANAEYABgAGAAYABgAGAAYABgANgEYADcBOQEYADsBPQE/AQEBQwFFAUcBSQFLAU0BWQEPAVEBUsFUwVbBWAAYgVgAGoFcgV6BYIFigWRBWAAmQWfBaYFYABgAGAAYABgAKoFYACxBbAFuQW6BcEFwQXHBcEFwQXPBdMF2wXjBeoF8gX6BQIGCgYSBhoGIgYqBjIGOgZgAD4GRgZMBmAAUwZaBmAAYABgAGAAYABgAGAAYABgAGAAYABgAGIGYABpBnAGYABgAGAAYABgAGAAYABgAGAAYAB4Bn8GhQZgAGAAYAB1AHcDFQSLBmAAYABgAJMGdQA9A3UAmwajBqsGqwaVALMGuwbDBjAAywbSBtIG1QbSBtIG0gbSBtIG0gbdBuMG6wbzBvsGAwcLBxMHAwcbByMHJwcsBywHMQcsB9IGOAdAB0gHTgfSBkgHVgfSBtIG0gbSBtIG0gbSBtIG0gbSBiwHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAdgAGAALAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAdbB2MHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsB2kH0gZwB64EdQB1AHUAdQB1AHUAdQB1AHUHfQdgAIUHjQd1AHUAlQedB2AAYAClB6sHYACzB7YHvgfGB3UAzgfWBzMB3gfmB1EB7gf1B/0HlQENAQUIDQh1ABUIHQglCBcDLQg1CD0IRQhNCEEDUwh1AHUAdQBbCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIaQhjCGQIZQhmCGcIaAhpCGMIZAhlCGYIZwhoCGkIYwhkCGUIZghnCGgIcAh3CHoIMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIgggwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAALAcsBywHLAcsBywHLAcsBywHLAcsB4oILAcsB44I0gaWCJ4Ipgh1AHUAqgiyCHUAdQB1AHUAdQB1AHUAdQB1AHUAtwh8AXUAvwh1AMUIyQjRCNkI4AjoCHUAdQB1AO4I9gj+CAYJDgkTCS0HGwkjCYIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiCCIIIggiAAIAAAAFAAYABgAGIAXwBgAHEAdQBFAJUAogCyAKAAYABgAEIA4ABGANMA4QDxAMEBDwE1AFwBLAE6AQEBUQF4QkhCmEKoQrhCgAHIQsAB0MLAAcABwAHAAeDC6ABoAHDCwMMAAcABwAHAAdDDGMMAAcAB6MM4wwjDWMNow3jDaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAEjDqABWw6bDqABpg6gAaABoAHcDvwOPA+gAaABfA/8DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DvwO/A78DpcPAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcAB9cPKwkyCToJMAB1AHUAdQBCCUoJTQl1AFUJXAljCWcJawkwADAAMAAwAHMJdQB2CX4JdQCECYoJjgmWCXUAngkwAGAAYABxAHUApgn3A64JtAl1ALkJdQDACTAAMAAwADAAdQB1AHUAdQB1AHUAdQB1AHUAowYNBMUIMAAwADAAMADICcsJ0wnZCRUE4QkwAOkJ8An4CTAAMAB1AAAKvwh1AAgKDwoXCh8KdQAwACcKLgp1ADYKqAmICT4KRgowADAAdQB1AE4KMAB1AFYKdQBeCnUAZQowADAAMAAwADAAMAAwADAAMAAVBHUAbQowADAAdQC5CXUKMAAwAHwBxAijBogEMgF9CoQKiASMCpQKmgqIBKIKqgquCogEDQG2Cr4KxgrLCjAAMADTCtsKCgHjCusK8Qr5CgELMAAwADAAMAB1AIsECQsRC3UANAEZCzAAMAAwADAAMAB1ACELKQswAHUANAExCzkLdQBBC0kLMABRC1kLMAAwADAAMAAwADAAdQBhCzAAMAAwAGAAYABpC3ELdwt/CzAAMACHC4sLkwubC58Lpwt1AK4Ltgt1APsDMAAwADAAMAAwADAAMAAwAL4LwwvLC9IL1wvdCzAAMADlC+kL8Qv5C/8LSQswADAAMAAwADAAMAAwADAAMAAHDDAAMAAwADAAMAAODBYMHgx1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1ACYMMAAwADAAdQB1AHUALgx1AHUAdQB1AHUAdQA2DDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AD4MdQBGDHUAdQB1AHUAdQB1AEkMdQB1AHUAdQB1AFAMMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQBYDHUAdQB1AF8MMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUA+wMVBGcMMAAwAHwBbwx1AHcMfwyHDI8MMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAYABgAJcMMAAwADAAdQB1AJ8MlQClDDAAMACtDCwHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsB7UMLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHdQB1AHUAdQB1AHUAdQB1AHUAdQB1AHUAdQB1AA0EMAC9DDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAsBywHLAcsBywHLAcsBywHLQcwAMEMyAwsBywHLAcsBywHLAcsBywHLAcsBywHzAwwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAHUAdQB1ANQM2QzhDDAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMABgAGAAYABgAGAAYABgAOkMYADxDGAA+AwADQYNYABhCWAAYAAODTAAMAAwADAAFg1gAGAAHg37AzAAMAAwADAAYABgACYNYAAsDTQNPA1gAEMNPg1LDWAAYABgAGAAYABgAGAAYABgAGAAUg1aDYsGVglhDV0NcQBnDW0NdQ15DWAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAlQCBDZUAiA2PDZcNMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAnw2nDTAAMAAwADAAMAAwAHUArw23DTAAMAAwADAAMAAwADAAMAAwADAAMAB1AL8NMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAB1AHUAdQB1AHUAdQDHDTAAYABgAM8NMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAA1w11ANwNMAAwAD0B5A0wADAAMAAwADAAMADsDfQN/A0EDgwOFA4wABsOMAAwADAAMAAwADAAMAAwANIG0gbSBtIG0gbSBtIG0gYjDigOwQUuDsEFMw7SBjoO0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGQg5KDlIOVg7SBtIGXg5lDm0OdQ7SBtIGfQ6EDooOjQ6UDtIGmg6hDtIG0gaoDqwO0ga0DrwO0gZgAGAAYADEDmAAYAAkBtIGzA5gANIOYADaDokO0gbSBt8O5w7SBu8O0gb1DvwO0gZgAGAAxA7SBtIG0gbSBtIGYABgAGAAYAAED2AAsAUMD9IG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGFA8sBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAccD9IGLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHJA8sBywHLAcsBywHLAccDywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywPLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAc0D9IG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAccD9IG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIGFA8sBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHLAcsBywHPA/SBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gbSBtIG0gYUD0QPlQCVAJUAMAAwADAAMACVAJUAlQCVAJUAlQCVAEwPMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAA//8EAAQABAAEAAQABAAEAAQABAANAAMAAQABAAIABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQACgATABcAHgAbABoAHgAXABYAEgAeABsAGAAPABgAHABLAEsASwBLAEsASwBLAEsASwBLABgAGAAeAB4AHgATAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABYAGwASAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAWAA0AEQAeAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAFAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAJABYAGgAbABsAGwAeAB0AHQAeAE8AFwAeAA0AHgAeABoAGwBPAE8ADgBQAB0AHQAdAE8ATwAXAE8ATwBPABYAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AHgAeAFAATwBAAE8ATwBPAEAATwBQAFAATwBQAB4AHgAeAB4AHgAeAB0AHQAdAB0AHgAdAB4ADgBQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgBQAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAJAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAkACQAJAAkACQAJAAkABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAFAAHgAeAB4AKwArAFAAUABQAFAAGABQACsAKwArACsAHgAeAFAAHgBQAFAAUAArAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAUAAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAYAA0AKwArAB4AHgAbACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQADQAEAB4ABAAEAB4ABAAEABMABAArACsAKwArACsAKwArACsAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAKwArACsAKwBWAFYAVgBWAB4AHgArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AGgAaABoAGAAYAB4AHgAEAAQABAAEAAQABAAEAAQABAAEAAQAEwAEACsAEwATAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABLAEsASwBLAEsASwBLAEsASwBLABoAGQAZAB4AUABQAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQABMAUAAEAAQABAAEAAQABAAEAB4AHgAEAAQABAAEAAQABABQAFAABAAEAB4ABAAEAAQABABQAFAASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUAAeAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAFAABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQAUABQAB4AHgAYABMAUAArACsABAAbABsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAFAABAAEAAQABAAEAFAABAAEAAQAUAAEAAQABAAEAAQAKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAArACsAHgArAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAUAAEAAQABAAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAABAAEAA0ADQBLAEsASwBLAEsASwBLAEsASwBLAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUAArACsAKwBQAFAAUABQACsAKwAEAFAABAAEAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABABQACsAKwArACsAKwArACsAKwAEACsAKwArACsAUABQACsAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAFAAUAAaABoAUABQAFAAUABQAEwAHgAbAFAAHgAEACsAKwAEAAQABAArAFAAUABQAFAAUABQACsAKwArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQACsAUABQACsAKwAEACsABAAEAAQABAAEACsAKwArACsABAAEACsAKwAEAAQABAArACsAKwAEACsAKwArACsAKwArACsAUABQAFAAUAArAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLAAQABABQAFAAUAAEAB4AKwArACsAKwArACsAKwArACsAKwAEAAQABAArAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQACsAKwAEAFAABAAEAAQABAAEAAQABAAEACsABAAEAAQAKwAEAAQABAArACsAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAB4AGwArACsAKwArACsAKwArAFAABAAEAAQABAAEAAQAKwAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABAArACsAKwArACsAKwArAAQABAAEACsAKwArACsAUABQACsAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAB4AUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArAAQAUAArAFAAUABQAFAAUABQACsAKwArAFAAUABQACsAUABQAFAAUAArACsAKwBQAFAAKwBQACsAUABQACsAKwArAFAAUAArACsAKwBQAFAAUAArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArAAQABAAEAAQABAArACsAKwAEAAQABAArAAQABAAEAAQAKwArAFAAKwArACsAKwArACsABAArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAHgAeAB4AHgAeAB4AGwAeACsAKwArACsAKwAEAAQABAAEAAQAUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAUAAEAAQABAAEAAQABAAEACsABAAEAAQAKwAEAAQABAAEACsAKwArACsAKwArACsABAAEACsAUABQAFAAKwArACsAKwArAFAAUAAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAKwAOAFAAUABQAFAAUABQAFAAHgBQAAQABAAEAA4AUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAKwArAAQAUAAEAAQABAAEAAQABAAEACsABAAEAAQAKwAEAAQABAAEACsAKwArACsAKwArACsABAAEACsAKwArACsAKwArACsAUAArAFAAUAAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwBQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAFAABAAEAAQABAAEAAQABAArAAQABAAEACsABAAEAAQABABQAB4AKwArACsAKwBQAFAAUAAEAFAAUABQAFAAUABQAFAAUABQAFAABAAEACsAKwBLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAFAAUABQAFAAUABQABoAUABQAFAAUABQAFAAKwAEAAQABAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQACsAUAArACsAUABQAFAAUABQAFAAUAArACsAKwAEACsAKwArACsABAAEAAQABAAEAAQAKwAEACsABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArAAQABAAeACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqAFwAXAAqACoAKgAqACoAKgAqACsAKwArACsAGwBcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAeAEsASwBLAEsASwBLAEsASwBLAEsADQANACsAKwArACsAKwBcAFwAKwBcACsAXABcAFwAXABcACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACsAXAArAFwAXABcAFwAXABcAFwAXABcAFwAKgBcAFwAKgAqACoAKgAqACoAKgAqACoAXAArACsAXABcAFwAXABcACsAXAArACoAKgAqACoAKgAqACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwBcAFwAXABcAFAADgAOAA4ADgAeAA4ADgAJAA4ADgANAAkAEwATABMAEwATAAkAHgATAB4AHgAeAAQABAAeAB4AHgAeAB4AHgBLAEsASwBLAEsASwBLAEsASwBLAFAAUABQAFAAUABQAFAAUABQAFAADQAEAB4ABAAeAAQAFgARABYAEQAEAAQAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQADQAEAAQABAAEAAQADQAEAAQAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArAA0ADQAeAB4AHgAeAB4AHgAEAB4AHgAeAB4AHgAeACsAHgAeAA4ADgANAA4AHgAeAB4AHgAeAAkACQArACsAKwArACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgBcAEsASwBLAEsASwBLAEsASwBLAEsADQANAB4AHgAeAB4AXABcAFwAXABcAFwAKgAqACoAKgBcAFwAXABcACoAKgAqAFwAKgAqACoAXABcACoAKgAqACoAKgAqACoAXABcAFwAKgAqACoAKgBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAqACoAKgAqAFwAKgBLAEsASwBLAEsASwBLAEsASwBLACoAKgAqACoAKgAqAFAAUABQAFAAUABQACsAUAArACsAKwArACsAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgBQAFAAUABQAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUAArACsAUABQAFAAUABQAFAAUAArAFAAKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAKwBQACsAUABQAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsABAAEAAQAHgANAB4AHgAeAB4AHgAeAB4AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUAArACsADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAANAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAWABEAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAA0ADQANAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAANAA0AKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUAArAAQABAArACsAKwArACsAKwArACsAKwArACsAKwBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqAA0ADQAVAFwADQAeAA0AGwBcACoAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwAeAB4AEwATAA0ADQAOAB4AEwATAB4ABAAEAAQACQArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUAAEAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAHgArACsAKwATABMASwBLAEsASwBLAEsASwBLAEsASwBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAArACsAXABcAFwAXABcACsAKwArACsAKwArACsAKwArACsAKwBcAFwAXABcAFwAXABcAFwAXABcAFwAXAArACsAKwArAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAXAArACsAKwAqACoAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAArACsAHgAeAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcACoAKgAqACoAKgAqACoAKgAqACoAKwAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKwArAAQASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArACoAKgAqACoAKgAqACoAXAAqACoAKgAqACoAKgArACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABABQAFAAUABQAFAAUABQACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwANAA0AHgANAA0ADQANAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAEAAQABAAEAAQAHgAeAB4AHgAeAB4AHgAeAB4AKwArACsABAAEAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwAeAB4AHgAeAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArAA0ADQANAA0ADQBLAEsASwBLAEsASwBLAEsASwBLACsAKwArAFAAUABQAEsASwBLAEsASwBLAEsASwBLAEsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAA0ADQBQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUAAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArAAQABAAEAB4ABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAAQAUABQAFAAUABQAFAABABQAFAABAAEAAQAUAArACsAKwArACsABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsABAAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAKwBQACsAUAArAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgBQAB4AHgAeAFAAUABQACsAHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQACsAKwAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAFAAUABQACsAHgAeAB4AHgAeAB4AHgAOAB4AKwANAA0ADQANAA0ADQANAAkADQANAA0ACAAEAAsABAAEAA0ACQANAA0ADAAdAB0AHgAXABcAFgAXABcAFwAWABcAHQAdAB4AHgAUABQAFAANAAEAAQAEAAQABAAEAAQACQAaABoAGgAaABoAGgAaABoAHgAXABcAHQAVABUAHgAeAB4AHgAeAB4AGAAWABEAFQAVABUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ADQAeAA0ADQANAA0AHgANAA0ADQAHAB4AHgAeAB4AKwAEAAQABAAEAAQABAAEAAQABAAEAFAAUAArACsATwBQAFAAUABQAFAAHgAeAB4AFgARAE8AUABPAE8ATwBPAFAAUABQAFAAUAAeAB4AHgAWABEAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArABsAGwAbABsAGwAbABsAGgAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGgAbABsAGwAbABoAGwAbABoAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbABsAGwAbAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAHgAeAFAAGgAeAB0AHgBQAB4AGgAeAB4AHgAeAB4AHgAeAB4AHgBPAB4AUAAbAB4AHgBQAFAAUABQAFAAHgAeAB4AHQAdAB4AUAAeAFAAHgBQAB4AUABPAFAAUAAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAHgBQAFAAUABQAE8ATwBQAFAAUABQAFAATwBQAFAATwBQAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAFAAUABQAFAATwBPAE8ATwBPAE8ATwBPAE8ATwBQAFAAUABQAFAAUABQAFAAUAAeAB4AUABQAFAAUABPAB4AHgArACsAKwArAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHQAdAB4AHgAeAB0AHQAeAB4AHQAeAB4AHgAdAB4AHQAbABsAHgAdAB4AHgAeAB4AHQAeAB4AHQAdAB0AHQAeAB4AHQAeAB0AHgAdAB0AHQAdAB0AHQAeAB0AHgAeAB4AHgAeAB0AHQAdAB0AHgAeAB4AHgAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB4AHgAeAB0AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAeAB0AHQAdAB0AHgAeAB0AHQAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAeAB4AHgAdAB4AHgAeAB4AHgAeAB4AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABYAEQAWABEAHgAeAB4AHgAeAB4AHQAeAB4AHgAeAB4AHgAeACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAWABEAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAFAAHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAeAB4AHQAdAB0AHQAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB0AHQAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB0AHQAeAB4AHQAdAB4AHgAeAB4AHQAdAB4AHgAeAB4AHQAdAB0AHgAeAB0AHgAeAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlAB4AHQAdAB4AHgAdAB4AHgAeAB4AHQAdAB4AHgAeAB4AJQAlAB0AHQAlAB4AJQAlACUAIAAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAeAB4AHgAeAB0AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHgAdAB0AHQAeAB0AJQAdAB0AHgAdAB0AHgAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHQAdAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAdAB0AHQAdACUAHgAlACUAJQAdACUAJQAdAB0AHQAlACUAHQAdACUAHQAdACUAJQAlAB4AHQAeAB4AHgAeAB0AHQAlAB0AHQAdAB0AHQAdACUAJQAlACUAJQAdACUAJQAgACUAHQAdACUAJQAlACUAJQAlACUAJQAeAB4AHgAlACUAIAAgACAAIAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB0AHgAeAB4AFwAXABcAFwAXABcAHgATABMAJQAeAB4AHgAWABEAFgARABYAEQAWABEAFgARABYAEQAWABEATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAWABEAFgARABYAEQAWABEAFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFgARABYAEQAWABEAFgARABYAEQAWABEAFgARABYAEQAWABEAFgARABYAEQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAWABEAFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AFgARAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAdAB0AHQAdAB0AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUABQAFAAUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAEAAQABAAeAB4AKwArACsAKwArABMADQANAA0AUAATAA0AUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUAANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAA0ADQANAA0ADQANAA0ADQAeAA0AFgANAB4AHgAXABcAHgAeABcAFwAWABEAFgARABYAEQAWABEADQANAA0ADQATAFAADQANAB4ADQANAB4AHgAeAB4AHgAMAAwADQANAA0AHgANAA0AFgANAA0ADQANAA0ADQANAA0AHgANAB4ADQANAB4AHgAeACsAKwArACsAKwArACsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwArACsAKwArACsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArAA0AEQARACUAJQBHAFcAVwAWABEAFgARABYAEQAWABEAFgARACUAJQAWABEAFgARABYAEQAWABEAFQAWABEAEQAlAFcAVwBXAFcAVwBXAFcAVwBXAAQABAAEAAQABAAEACUAVwBXAFcAVwA2ACUAJQBXAFcAVwBHAEcAJQAlACUAKwBRAFcAUQBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFEAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBRAFcAUQBXAFEAVwBXAFcAVwBXAFcAUQBXAFcAVwBXAFcAVwBRAFEAKwArAAQABAAVABUARwBHAFcAFQBRAFcAUQBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFEAVwBRAFcAUQBXAFcAVwBXAFcAVwBRAFcAVwBXAFcAVwBXAFEAUQBXAFcAVwBXABUAUQBHAEcAVwArACsAKwArACsAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwAlACUAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACsAKwArACsAKwArACsAKwArACsAKwArAFEAUQBRAFEAUQBRAFEAUQBRAFEAUQBRAFEAUQBRAFEAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBPAE8ATwBPAE8ATwBPAE8AJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAEcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAADQATAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABLAEsASwBLAEsASwBLAEsASwBLAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAABAAEAAQABAAeAAQABAAEAAQABAAEAAQABAAEAAQAHgBQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUABQAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAeAA0ADQANAA0ADQArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AUAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAB4AHgAeAB4AHgAeAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AUABQAFAAUABQAFAAUABQAFAAUABQAAQAUABQAFAABABQAFAAUABQAAQAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAeAB4AHgAeAAQAKwArACsAUABQAFAAUABQAFAAHgAeABoAHgArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAADgAOABMAEwArACsAKwArACsAKwArACsABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwANAA0ASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUAAeAB4AHgBQAA4AUABQAAQAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAA0ADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArAB4AWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYAFgAWABYACsAKwArAAQAHgAeAB4AHgAeAB4ADQANAA0AHgAeAB4AHgArAFAASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArAB4AHgBcAFwAXABcAFwAKgBcAFwAXABcAFwAXABcAFwAXABcAEsASwBLAEsASwBLAEsASwBLAEsAXABcAFwAXABcACsAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArAFAAUABQAAQAUABQAFAAUABQAFAAUABQAAQABAArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAHgANAA0ADQBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKgAqACoAXAAqACoAKgBcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXAAqAFwAKgAqACoAXABcACoAKgBcAFwAXABcAFwAKgAqAFwAKgBcACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFwAXABcACoAKgBQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAA0ADQBQAFAAUAAEAAQAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUAArACsAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQADQAEAAQAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAVABVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBUAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVAFUAVQBVACsAKwArACsAKwArACsAKwArACsAKwArAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAWQBZAFkAKwArACsAKwBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAWgBaAFoAKwArACsAKwAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYABgAGAAYAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAKwArACsAKwArAFYABABWAFYAVgBWAFYAVgBWAFYAVgBWAB4AVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgArAFYAVgBWAFYAVgArAFYAKwBWAFYAKwBWAFYAKwBWAFYAVgBWAFYAVgBWAFYAVgBWAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAEQAWAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAaAB4AKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAGAARABEAGAAYABMAEwAWABEAFAArACsAKwArACsAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACUAJQAlACUAJQAWABEAFgARABYAEQAWABEAFgARABYAEQAlACUAFgARACUAJQAlACUAJQAlACUAEQAlABEAKwAVABUAEwATACUAFgARABYAEQAWABEAJQAlACUAJQAlACUAJQAlACsAJQAbABoAJQArACsAKwArAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAcAKwATACUAJQAbABoAJQAlABYAEQAlACUAEQAlABEAJQBXAFcAVwBXAFcAVwBXAFcAVwBXABUAFQAlACUAJQATACUAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXABYAJQARACUAJQAlAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAWACUAEQAlABYAEQARABYAEQARABUAVwBRAFEAUQBRAFEAUQBRAFEAUQBRAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAEcARwArACsAVwBXAFcAVwBXAFcAKwArAFcAVwBXAFcAVwBXACsAKwBXAFcAVwBXAFcAVwArACsAVwBXAFcAKwArACsAGgAbACUAJQAlABsAGwArAB4AHgAeAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwAEAAQABAAQAB0AKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsADQANAA0AKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAB4AHgAeAB4AHgAeAB4AHgAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAAQAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAA0AUABQAFAAUAArACsAKwArAFAAUABQAFAAUABQAFAAUAANAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwAeACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAKwArAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUAArACsAKwBQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwANAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAB4AUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAUABQAFAAUABQAAQABAAEACsABAAEACsAKwArACsAKwAEAAQABAAEAFAAUABQAFAAKwBQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEACsAKwArACsABABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAA0ADQANAA0ADQANAA0ADQAeACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAArACsAKwArAFAAUABQAFAAUAANAA0ADQANAA0ADQAUACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsADQANAA0ADQANAA0ADQBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAB4AHgAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAAQABAAEAAQAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUAArAAQABAANACsAKwBQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAB4AHgAeAB4AHgArACsAKwArACsAKwAEAAQABAAEAAQABAAEAA0ADQAeAB4AHgAeAB4AKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwAeACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEACsASwBLAEsASwBLAEsASwBLAEsASwANAA0ADQANAFAABAAEAFAAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAeAA4AUAArACsAKwArACsAKwArACsAKwAEAFAAUABQAFAADQANAB4ADQAEAAQABAAEAB4ABAAEAEsASwBLAEsASwBLAEsASwBLAEsAUAAOAFAADQANAA0AKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAANAA0AHgANAA0AHgAEACsAUABQAFAAUABQAFAAUAArAFAAKwBQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAA0AKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsABAAEAAQABAArAFAAUABQAFAAUABQAFAAUAArACsAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQACsABAAEAFAABAAEAAQABAAEAAQABAArACsABAAEACsAKwAEAAQABAArACsAUAArACsAKwArACsAKwAEACsAKwArACsAKwBQAFAAUABQAFAABAAEACsAKwAEAAQABAAEAAQABAAEACsAKwArAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsABAAEAAQABAAEAAQABABQAFAAUABQAA0ADQANAA0AHgBLAEsASwBLAEsASwBLAEsASwBLAA0ADQArAB4ABABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAAQABAAEAFAAUAAeAFAAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAArACsABAAEAAQABAAEAAQABAAEAAQADgANAA0AEwATAB4AHgAeAA0ADQANAA0ADQANAA0ADQANAA0ADQANAA0ADQANAFAAUABQAFAABAAEACsAKwAEAA0ADQAeAFAAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAFAAKwArACsAKwArACsAKwBLAEsASwBLAEsASwBLAEsASwBLACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAXABcAFwAKwArACoAKgAqACoAKgAqACoAKgAqACoAKgAqACoAKgAqACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBcAFwADQANAA0AKgBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAKwArAFAAKwArAFAAUABQAFAAUABQAFAAUAArAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQAKwAEAAQAKwArAAQABAAEAAQAUAAEAFAABAAEAA0ADQANACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAArACsABAAEAAQABAAEAAQABABQAA4AUAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAABAAEAAQABAAEAAQABAAEAAQABABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAFAABAAEAAQABAAOAB4ADQANAA0ADQAOAB4ABAArACsAKwArACsAKwArACsAUAAEAAQABAAEAAQABAAEAAQABAAEAAQAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAA0ADQANAFAADgAOAA4ADQANACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAEAAQABAAEACsABAAEAAQABAAEAAQABAAEAFAADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwAOABMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQACsAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAArACsAKwAEACsABAAEACsABAAEAAQABAAEAAQABABQAAQAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAUABQAFAAUABQAFAAKwBQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQAKwAEAAQAKwAEAAQABAAEAAQAUAArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAABAAEAAQABAAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAaABoAGgAaAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArAA0AUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsADQANAA0ADQANACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAASABIAEgAQwBDAEMAUABQAFAAUABDAFAAUABQAEgAQwBIAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAASABDAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwAJAAkACQAJAAkACQAJABYAEQArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABIAEMAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwANAA0AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArAAQABAAEAAQABAANACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEAA0ADQANAB4AHgAeAB4AHgAeAFAAUABQAFAADQAeACsAKwArACsAKwArACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAANAA0AHgAeACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwAEAFAABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwAEAAQABAAEAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAARwBHABUARwAJACsAKwArACsAKwArACsAKwArACsAKwAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACsAKwArACsAKwArACsAKwBXAFcAVwBXAFcAVwBXAFcAVwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUQBRAFEAKwArACsAKwArACsAKwArACsAKwArACsAKwBRAFEAUQBRACsAKwArACsAKwArACsAKwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUAArACsAHgAEAAQADQAEAAQABAAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AHgAeAB4AKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAAQABAAEAAQABAAeAB4AHgAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAB4AHgAEAAQABAAEAAQABAAEAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4ABAAEAAQAHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwArACsAKwArACsAKwArACsAKwArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwBQAFAAKwArAFAAKwArAFAAUAArACsAUABQAFAAUAArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACsAUAArAFAAUABQAFAAUABQAFAAKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwBQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAHgAeAFAAUABQAFAAUAArAFAAKwArACsAUABQAFAAUABQAFAAUAArAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAB4AHgAeAB4AHgAeAB4AHgAeACsAKwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAEsASwBLAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAeAB4AHgAeAB4AHgAeAB4ABAAeAB4AHgAeAB4AHgAeAB4AHgAeAAQAHgAeAA0ADQANAA0AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAEAAQABAAEAAQAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQAKwAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArAAQABAAEAAQABAAEAAQAKwAEAAQAKwAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwAEAAQABAAEAAQABAAEAFAAUABQAFAAUABQAFAAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwBQAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArABsAUABQAFAAUABQACsAKwBQAFAAUABQAFAAUABQAFAAUAAEAAQABAAEAAQABAAEACsAKwArACsAKwArACsAKwArAB4AHgAeAB4ABAAEAAQABAAEAAQABABQACsAKwArACsASwBLAEsASwBLAEsASwBLAEsASwArACsAKwArABYAFgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAGgBQAFAAUAAaAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAeAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQACsAKwBQAFAAUABQACsAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwBQAFAAKwBQACsAKwBQACsAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAKwBQACsAUAArACsAKwArACsAKwBQACsAKwArACsAUAArAFAAKwBQACsAUABQAFAAKwBQAFAAKwBQACsAKwBQACsAUAArAFAAKwBQACsAUAArAFAAUAArAFAAKwArAFAAUABQAFAAKwBQAFAAUABQAFAAUABQACsAUABQAFAAUAArAFAAUABQAFAAKwBQACsAUABQAFAAUABQAFAAUABQAFAAUAArAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAArACsAKwArACsAUABQAFAAKwBQAFAAUABQAFAAKwBQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwAeAB4AKwArACsAKwArACsAKwArACsAKwArACsAKwArAE8ATwBPAE8ATwBPAE8ATwBPAE8ATwBPAE8AJQAlACUAHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHgAeAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB4AHgAeACUAJQAlAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAdAB0AHQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAKQApACkAJQAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAlACUAJQAlACUAHgAlACUAJQAlACUAIAAgACAAJQAlACAAJQAlACAAIAAgACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACEAIQAhACEAIQAlACUAIAAgACUAJQAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlACUAIAAlACUAJQAlACAAIAAgACUAIAAgACAAJQAlACUAJQAlACUAJQAgACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAlAB4AJQAeACUAJQAlACUAJQAgACUAJQAlACUAHgAlAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAJQAlACUAJQAgACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACAAIAAgACUAJQAlACAAIAAgACAAIAAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeABcAFwAXABUAFQAVAB4AHgAeAB4AJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAgACUAJQAlACUAJQAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlACUAJQAeAB4AHgAeAB4AHgAeAB4AHgAeACUAJQAlACUAJQAlAB4AHgAeAB4AHgAeAB4AHgAlACUAJQAlACUAJQAlACUAHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAgACUAJQAgACUAJQAlACUAJQAlACUAJQAgACAAIAAgACAAIAAgACAAJQAlACUAJQAlACUAIAAlACUAJQAlACUAJQAlACUAJQAgACAAIAAgACAAIAAgACAAIAAgACUAJQAgACAAIAAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAgACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACAAIAAlACAAIAAlACAAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAgACAAIAAlACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAJQAlAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AKwAeAB4AHgAeAB4AHgAeAB4AHgAeAB4AHgArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAEsASwBLAEsASwBLAEsASwBLAEsAKwArACsAKwArACsAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwArAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwAlACUAJQAlACUAJQAlACUAJQAlACUAVwBXACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQBXAFcAVwBXAFcAVwBXAFcAVwBXAFcAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAJQAlACUAKwAEACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArACsAKwArAA=='; + + var LETTER_NUMBER_MODIFIER = 50; + // Non-tailorable Line Breaking Classes + var BK = 1; // Cause a line break (after) + var CR$1 = 2; // Cause a line break (after), except between CR and LF + var LF$1 = 3; // Cause a line break (after) + var CM = 4; // Prohibit a line break between the character and the preceding character + var NL = 5; // Cause a line break (after) + var WJ = 7; // Prohibit line breaks before and after + var ZW = 8; // Provide a break opportunity + var GL = 9; // Prohibit line breaks before and after + var SP = 10; // Enable indirect line breaks + var ZWJ$1 = 11; // Prohibit line breaks within joiner sequences + // Break Opportunities + var B2 = 12; // Provide a line break opportunity before and after the character + var BA = 13; // Generally provide a line break opportunity after the character + var BB = 14; // Generally provide a line break opportunity before the character + var HY = 15; // Provide a line break opportunity after the character, except in numeric context + var CB = 16; // Provide a line break opportunity contingent on additional information + // Characters Prohibiting Certain Breaks + var CL = 17; // Prohibit line breaks before + var CP = 18; // Prohibit line breaks before + var EX = 19; // Prohibit line breaks before + var IN = 20; // Allow only indirect line breaks between pairs + var NS = 21; // Allow only indirect line breaks before + var OP = 22; // Prohibit line breaks after + var QU = 23; // Act like they are both opening and closing + // Numeric Context + var IS = 24; // Prevent breaks after any and before numeric + var NU = 25; // Form numeric expressions for line breaking purposes + var PO = 26; // Do not break following a numeric expression + var PR = 27; // Do not break in front of a numeric expression + var SY = 28; // Prevent a break before; and allow a break after + // Other Characters + var AI = 29; // Act like AL when the resolvedEAW is N; otherwise; act as ID + var AL = 30; // Are alphabetic characters or symbols that are used with alphabetic characters + var CJ = 31; // Treat as NS or ID for strict or normal breaking. + var EB = 32; // Do not break from following Emoji Modifier + var EM = 33; // Do not break from preceding Emoji Base + var H2 = 34; // Form Korean syllable blocks + var H3 = 35; // Form Korean syllable blocks + var HL = 36; // Do not break around a following hyphen; otherwise act as Alphabetic + var ID = 37; // Break before or after; except in some numeric context + var JL = 38; // Form Korean syllable blocks + var JV = 39; // Form Korean syllable blocks + var JT = 40; // Form Korean syllable blocks + var RI$1 = 41; // Keep pairs together. For pairs; break before and after other classes + var SA = 42; // Provide a line break opportunity contingent on additional, language-specific context analysis + var XX = 43; // Have as yet unknown line breaking behavior or unassigned code positions + var ea_OP = [0x2329, 0xff08]; + var BREAK_MANDATORY = '!'; + var BREAK_NOT_ALLOWED$1 = '×'; + var BREAK_ALLOWED$1 = '÷'; + var UnicodeTrie$1 = createTrieFromBase64$1(base64$1); + var ALPHABETICS = [AL, HL]; + var HARD_LINE_BREAKS = [BK, CR$1, LF$1, NL]; + var SPACE$1 = [SP, ZW]; + var PREFIX_POSTFIX = [PR, PO]; + var LINE_BREAKS = HARD_LINE_BREAKS.concat(SPACE$1); + var KOREAN_SYLLABLE_BLOCK = [JL, JV, JT, H2, H3]; + var HYPHEN = [HY, BA]; + var codePointsToCharacterClasses = function (codePoints, lineBreak) { + if (lineBreak === void 0) { lineBreak = 'strict'; } + var types = []; + var indices = []; + var categories = []; + codePoints.forEach(function (codePoint, index) { + var classType = UnicodeTrie$1.get(codePoint); + if (classType > LETTER_NUMBER_MODIFIER) { + categories.push(true); + classType -= LETTER_NUMBER_MODIFIER; + } + else { + categories.push(false); + } + if (['normal', 'auto', 'loose'].indexOf(lineBreak) !== -1) { + // U+2010, – U+2013, 〜 U+301C, ゠ U+30A0 + if ([0x2010, 0x2013, 0x301c, 0x30a0].indexOf(codePoint) !== -1) { + indices.push(index); + return types.push(CB); + } + } + if (classType === CM || classType === ZWJ$1) { + // LB10 Treat any remaining combining mark or ZWJ as AL. + if (index === 0) { + indices.push(index); + return types.push(AL); + } + // LB9 Do not break a combining character sequence; treat it as if it has the line breaking class of + // the base character in all of the following rules. Treat ZWJ as if it were CM. + var prev = types[index - 1]; + if (LINE_BREAKS.indexOf(prev) === -1) { + indices.push(indices[index - 1]); + return types.push(prev); + } + indices.push(index); + return types.push(AL); + } + indices.push(index); + if (classType === CJ) { + return types.push(lineBreak === 'strict' ? NS : ID); + } + if (classType === SA) { + return types.push(AL); + } + if (classType === AI) { + return types.push(AL); + } + // For supplementary characters, a useful default is to treat characters in the range 10000..1FFFD as AL + // and characters in the ranges 20000..2FFFD and 30000..3FFFD as ID, until the implementation can be revised + // to take into account the actual line breaking properties for these characters. + if (classType === XX) { + if ((codePoint >= 0x20000 && codePoint <= 0x2fffd) || (codePoint >= 0x30000 && codePoint <= 0x3fffd)) { + return types.push(ID); + } + else { + return types.push(AL); + } + } + types.push(classType); + }); + return [indices, types, categories]; + }; + var isAdjacentWithSpaceIgnored = function (a, b, currentIndex, classTypes) { + var current = classTypes[currentIndex]; + if (Array.isArray(a) ? a.indexOf(current) !== -1 : a === current) { + var i = currentIndex; + while (i <= classTypes.length) { + i++; + var next = classTypes[i]; + if (next === b) { + return true; + } + if (next !== SP) { + break; + } + } + } + if (current === SP) { + var i = currentIndex; + while (i > 0) { + i--; + var prev = classTypes[i]; + if (Array.isArray(a) ? a.indexOf(prev) !== -1 : a === prev) { + var n = currentIndex; + while (n <= classTypes.length) { + n++; + var next = classTypes[n]; + if (next === b) { + return true; + } + if (next !== SP) { + break; + } + } + } + if (prev !== SP) { + break; + } + } + } + return false; + }; + var previousNonSpaceClassType = function (currentIndex, classTypes) { + var i = currentIndex; + while (i >= 0) { + var type = classTypes[i]; + if (type === SP) { + i--; + } + else { + return type; + } + } + return 0; + }; + var _lineBreakAtIndex = function (codePoints, classTypes, indicies, index, forbiddenBreaks) { + if (indicies[index] === 0) { + return BREAK_NOT_ALLOWED$1; + } + var currentIndex = index - 1; + if (Array.isArray(forbiddenBreaks) && forbiddenBreaks[currentIndex] === true) { + return BREAK_NOT_ALLOWED$1; + } + var beforeIndex = currentIndex - 1; + var afterIndex = currentIndex + 1; + var current = classTypes[currentIndex]; + // LB4 Always break after hard line breaks. + // LB5 Treat CR followed by LF, as well as CR, LF, and NL as hard line breaks. + var before = beforeIndex >= 0 ? classTypes[beforeIndex] : 0; + var next = classTypes[afterIndex]; + if (current === CR$1 && next === LF$1) { + return BREAK_NOT_ALLOWED$1; + } + if (HARD_LINE_BREAKS.indexOf(current) !== -1) { + return BREAK_MANDATORY; + } + // LB6 Do not break before hard line breaks. + if (HARD_LINE_BREAKS.indexOf(next) !== -1) { + return BREAK_NOT_ALLOWED$1; + } + // LB7 Do not break before spaces or zero width space. + if (SPACE$1.indexOf(next) !== -1) { + return BREAK_NOT_ALLOWED$1; + } + // LB8 Break before any character following a zero-width space, even if one or more spaces intervene. + if (previousNonSpaceClassType(currentIndex, classTypes) === ZW) { + return BREAK_ALLOWED$1; + } + // LB8a Do not break after a zero width joiner. + if (UnicodeTrie$1.get(codePoints[currentIndex]) === ZWJ$1) { + return BREAK_NOT_ALLOWED$1; + } + // zwj emojis + if ((current === EB || current === EM) && UnicodeTrie$1.get(codePoints[afterIndex]) === ZWJ$1) { + return BREAK_NOT_ALLOWED$1; + } + // LB11 Do not break before or after Word joiner and related characters. + if (current === WJ || next === WJ) { + return BREAK_NOT_ALLOWED$1; + } + // LB12 Do not break after NBSP and related characters. + if (current === GL) { + return BREAK_NOT_ALLOWED$1; + } + // LB12a Do not break before NBSP and related characters, except after spaces and hyphens. + if ([SP, BA, HY].indexOf(current) === -1 && next === GL) { + return BREAK_NOT_ALLOWED$1; + } + // LB13 Do not break before ‘]’ or ‘!’ or ‘;’ or ‘/’, even after spaces. + if ([CL, CP, EX, IS, SY].indexOf(next) !== -1) { + return BREAK_NOT_ALLOWED$1; + } + // LB14 Do not break after ‘[’, even after spaces. + if (previousNonSpaceClassType(currentIndex, classTypes) === OP) { + return BREAK_NOT_ALLOWED$1; + } + // LB15 Do not break within ‘”[’, even with intervening spaces. + if (isAdjacentWithSpaceIgnored(QU, OP, currentIndex, classTypes)) { + return BREAK_NOT_ALLOWED$1; + } + // LB16 Do not break between closing punctuation and a nonstarter (lb=NS), even with intervening spaces. + if (isAdjacentWithSpaceIgnored([CL, CP], NS, currentIndex, classTypes)) { + return BREAK_NOT_ALLOWED$1; + } + // LB17 Do not break within ‘——’, even with intervening spaces. + if (isAdjacentWithSpaceIgnored(B2, B2, currentIndex, classTypes)) { + return BREAK_NOT_ALLOWED$1; + } + // LB18 Break after spaces. + if (current === SP) { + return BREAK_ALLOWED$1; + } + // LB19 Do not break before or after quotation marks, such as ‘ ” ’. + if (current === QU || next === QU) { + return BREAK_NOT_ALLOWED$1; + } + // LB20 Break before and after unresolved CB. + if (next === CB || current === CB) { + return BREAK_ALLOWED$1; + } + // LB21 Do not break before hyphen-minus, other hyphens, fixed-width spaces, small kana, and other non-starters, or after acute accents. + if ([BA, HY, NS].indexOf(next) !== -1 || current === BB) { + return BREAK_NOT_ALLOWED$1; + } + // LB21a Don't break after Hebrew + Hyphen. + if (before === HL && HYPHEN.indexOf(current) !== -1) { + return BREAK_NOT_ALLOWED$1; + } + // LB21b Don’t break between Solidus and Hebrew letters. + if (current === SY && next === HL) { + return BREAK_NOT_ALLOWED$1; + } + // LB22 Do not break before ellipsis. + if (next === IN) { + return BREAK_NOT_ALLOWED$1; + } + // LB23 Do not break between digits and letters. + if ((ALPHABETICS.indexOf(next) !== -1 && current === NU) || (ALPHABETICS.indexOf(current) !== -1 && next === NU)) { + return BREAK_NOT_ALLOWED$1; + } + // LB23a Do not break between numeric prefixes and ideographs, or between ideographs and numeric postfixes. + if ((current === PR && [ID, EB, EM].indexOf(next) !== -1) || + ([ID, EB, EM].indexOf(current) !== -1 && next === PO)) { + return BREAK_NOT_ALLOWED$1; + } + // LB24 Do not break between numeric prefix/postfix and letters, or between letters and prefix/postfix. + if ((ALPHABETICS.indexOf(current) !== -1 && PREFIX_POSTFIX.indexOf(next) !== -1) || + (PREFIX_POSTFIX.indexOf(current) !== -1 && ALPHABETICS.indexOf(next) !== -1)) { + return BREAK_NOT_ALLOWED$1; + } + // LB25 Do not break between the following pairs of classes relevant to numbers: + if ( + // (PR | PO) × ( OP | HY )? NU + ([PR, PO].indexOf(current) !== -1 && + (next === NU || ([OP, HY].indexOf(next) !== -1 && classTypes[afterIndex + 1] === NU))) || + // ( OP | HY ) × NU + ([OP, HY].indexOf(current) !== -1 && next === NU) || + // NU × (NU | SY | IS) + (current === NU && [NU, SY, IS].indexOf(next) !== -1)) { + return BREAK_NOT_ALLOWED$1; + } + // NU (NU | SY | IS)* × (NU | SY | IS | CL | CP) + if ([NU, SY, IS, CL, CP].indexOf(next) !== -1) { + var prevIndex = currentIndex; + while (prevIndex >= 0) { + var type = classTypes[prevIndex]; + if (type === NU) { + return BREAK_NOT_ALLOWED$1; + } + else if ([SY, IS].indexOf(type) !== -1) { + prevIndex--; + } + else { + break; + } + } + } + // NU (NU | SY | IS)* (CL | CP)? × (PO | PR)) + if ([PR, PO].indexOf(next) !== -1) { + var prevIndex = [CL, CP].indexOf(current) !== -1 ? beforeIndex : currentIndex; + while (prevIndex >= 0) { + var type = classTypes[prevIndex]; + if (type === NU) { + return BREAK_NOT_ALLOWED$1; + } + else if ([SY, IS].indexOf(type) !== -1) { + prevIndex--; + } + else { + break; + } + } + } + // LB26 Do not break a Korean syllable. + if ((JL === current && [JL, JV, H2, H3].indexOf(next) !== -1) || + ([JV, H2].indexOf(current) !== -1 && [JV, JT].indexOf(next) !== -1) || + ([JT, H3].indexOf(current) !== -1 && next === JT)) { + return BREAK_NOT_ALLOWED$1; + } + // LB27 Treat a Korean Syllable Block the same as ID. + if ((KOREAN_SYLLABLE_BLOCK.indexOf(current) !== -1 && [IN, PO].indexOf(next) !== -1) || + (KOREAN_SYLLABLE_BLOCK.indexOf(next) !== -1 && current === PR)) { + return BREAK_NOT_ALLOWED$1; + } + // LB28 Do not break between alphabetics (“at”). + if (ALPHABETICS.indexOf(current) !== -1 && ALPHABETICS.indexOf(next) !== -1) { + return BREAK_NOT_ALLOWED$1; + } + // LB29 Do not break between numeric punctuation and alphabetics (“e.g.”). + if (current === IS && ALPHABETICS.indexOf(next) !== -1) { + return BREAK_NOT_ALLOWED$1; + } + // LB30 Do not break between letters, numbers, or ordinary symbols and opening or closing parentheses. + if ((ALPHABETICS.concat(NU).indexOf(current) !== -1 && + next === OP && + ea_OP.indexOf(codePoints[afterIndex]) === -1) || + (ALPHABETICS.concat(NU).indexOf(next) !== -1 && current === CP)) { + return BREAK_NOT_ALLOWED$1; + } + // LB30a Break between two regional indicator symbols if and only if there are an even number of regional + // indicators preceding the position of the break. + if (current === RI$1 && next === RI$1) { + var i = indicies[currentIndex]; + var count = 1; + while (i > 0) { + i--; + if (classTypes[i] === RI$1) { + count++; + } + else { + break; + } + } + if (count % 2 !== 0) { + return BREAK_NOT_ALLOWED$1; + } + } + // LB30b Do not break between an emoji base and an emoji modifier. + if (current === EB && next === EM) { + return BREAK_NOT_ALLOWED$1; + } + return BREAK_ALLOWED$1; + }; + var cssFormattedClasses = function (codePoints, options) { + if (!options) { + options = { lineBreak: 'normal', wordBreak: 'normal' }; + } + var _a = codePointsToCharacterClasses(codePoints, options.lineBreak), indicies = _a[0], classTypes = _a[1], isLetterNumber = _a[2]; + if (options.wordBreak === 'break-all' || options.wordBreak === 'break-word') { + classTypes = classTypes.map(function (type) { return ([NU, AL, SA].indexOf(type) !== -1 ? ID : type); }); + } + var forbiddenBreakpoints = options.wordBreak === 'keep-all' + ? isLetterNumber.map(function (letterNumber, i) { + return letterNumber && codePoints[i] >= 0x4e00 && codePoints[i] <= 0x9fff; + }) + : undefined; + return [indicies, classTypes, forbiddenBreakpoints]; + }; + var Break = /** @class */ (function () { + function Break(codePoints, lineBreak, start, end) { + this.codePoints = codePoints; + this.required = lineBreak === BREAK_MANDATORY; + this.start = start; + this.end = end; + } + Break.prototype.slice = function () { + return fromCodePoint$1.apply(void 0, this.codePoints.slice(this.start, this.end)); + }; + return Break; + }()); + var LineBreaker = function (str, options) { + var codePoints = toCodePoints$1(str); + var _a = cssFormattedClasses(codePoints, options), indicies = _a[0], classTypes = _a[1], forbiddenBreakpoints = _a[2]; + var length = codePoints.length; + var lastEnd = 0; + var nextIndex = 0; + return { + next: function () { + if (nextIndex >= length) { + return { done: true, value: null }; + } + var lineBreak = BREAK_NOT_ALLOWED$1; + while (nextIndex < length && + (lineBreak = _lineBreakAtIndex(codePoints, classTypes, indicies, ++nextIndex, forbiddenBreakpoints)) === + BREAK_NOT_ALLOWED$1) { } + if (lineBreak !== BREAK_NOT_ALLOWED$1 || nextIndex === length) { + var value = new Break(codePoints, lineBreak, lastEnd, nextIndex); + lastEnd = nextIndex; + return { value: value, done: false }; + } + return { done: true, value: null }; + }, + }; + }; + + // https://www.w3.org/TR/css-syntax-3 + var FLAG_UNRESTRICTED = 1 << 0; + var FLAG_ID = 1 << 1; + var FLAG_INTEGER = 1 << 2; + var FLAG_NUMBER = 1 << 3; + var LINE_FEED = 0x000a; + var SOLIDUS = 0x002f; + var REVERSE_SOLIDUS = 0x005c; + var CHARACTER_TABULATION = 0x0009; + var SPACE = 0x0020; + var QUOTATION_MARK = 0x0022; + var EQUALS_SIGN = 0x003d; + var NUMBER_SIGN = 0x0023; + var DOLLAR_SIGN = 0x0024; + var PERCENTAGE_SIGN = 0x0025; + var APOSTROPHE = 0x0027; + var LEFT_PARENTHESIS = 0x0028; + var RIGHT_PARENTHESIS = 0x0029; + var LOW_LINE = 0x005f; + var HYPHEN_MINUS = 0x002d; + var EXCLAMATION_MARK = 0x0021; + var LESS_THAN_SIGN = 0x003c; + var GREATER_THAN_SIGN = 0x003e; + var COMMERCIAL_AT = 0x0040; + var LEFT_SQUARE_BRACKET = 0x005b; + var RIGHT_SQUARE_BRACKET = 0x005d; + var CIRCUMFLEX_ACCENT = 0x003d; + var LEFT_CURLY_BRACKET = 0x007b; + var QUESTION_MARK = 0x003f; + var RIGHT_CURLY_BRACKET = 0x007d; + var VERTICAL_LINE = 0x007c; + var TILDE = 0x007e; + var CONTROL = 0x0080; + var REPLACEMENT_CHARACTER = 0xfffd; + var ASTERISK = 0x002a; + var PLUS_SIGN = 0x002b; + var COMMA = 0x002c; + var COLON = 0x003a; + var SEMICOLON = 0x003b; + var FULL_STOP = 0x002e; + var NULL = 0x0000; + var BACKSPACE = 0x0008; + var LINE_TABULATION = 0x000b; + var SHIFT_OUT = 0x000e; + var INFORMATION_SEPARATOR_ONE = 0x001f; + var DELETE = 0x007f; + var EOF = -1; + var ZERO = 0x0030; + var a = 0x0061; + var e = 0x0065; + var f = 0x0066; + var u = 0x0075; + var z = 0x007a; + var A = 0x0041; + var E = 0x0045; + var F = 0x0046; + var U = 0x0055; + var Z = 0x005a; + var isDigit = function (codePoint) { return codePoint >= ZERO && codePoint <= 0x0039; }; + var isSurrogateCodePoint = function (codePoint) { return codePoint >= 0xd800 && codePoint <= 0xdfff; }; + var isHex = function (codePoint) { + return isDigit(codePoint) || (codePoint >= A && codePoint <= F) || (codePoint >= a && codePoint <= f); + }; + var isLowerCaseLetter = function (codePoint) { return codePoint >= a && codePoint <= z; }; + var isUpperCaseLetter = function (codePoint) { return codePoint >= A && codePoint <= Z; }; + var isLetter = function (codePoint) { return isLowerCaseLetter(codePoint) || isUpperCaseLetter(codePoint); }; + var isNonASCIICodePoint = function (codePoint) { return codePoint >= CONTROL; }; + var isWhiteSpace = function (codePoint) { + return codePoint === LINE_FEED || codePoint === CHARACTER_TABULATION || codePoint === SPACE; + }; + var isNameStartCodePoint = function (codePoint) { + return isLetter(codePoint) || isNonASCIICodePoint(codePoint) || codePoint === LOW_LINE; + }; + var isNameCodePoint = function (codePoint) { + return isNameStartCodePoint(codePoint) || isDigit(codePoint) || codePoint === HYPHEN_MINUS; + }; + var isNonPrintableCodePoint = function (codePoint) { + return ((codePoint >= NULL && codePoint <= BACKSPACE) || + codePoint === LINE_TABULATION || + (codePoint >= SHIFT_OUT && codePoint <= INFORMATION_SEPARATOR_ONE) || + codePoint === DELETE); + }; + var isValidEscape = function (c1, c2) { + if (c1 !== REVERSE_SOLIDUS) { + return false; + } + return c2 !== LINE_FEED; + }; + var isIdentifierStart = function (c1, c2, c3) { + if (c1 === HYPHEN_MINUS) { + return isNameStartCodePoint(c2) || isValidEscape(c2, c3); + } + else if (isNameStartCodePoint(c1)) { + return true; + } + else if (c1 === REVERSE_SOLIDUS && isValidEscape(c1, c2)) { + return true; + } + return false; + }; + var isNumberStart = function (c1, c2, c3) { + if (c1 === PLUS_SIGN || c1 === HYPHEN_MINUS) { + if (isDigit(c2)) { + return true; + } + return c2 === FULL_STOP && isDigit(c3); + } + if (c1 === FULL_STOP) { + return isDigit(c2); + } + return isDigit(c1); + }; + var stringToNumber = function (codePoints) { + var c = 0; + var sign = 1; + if (codePoints[c] === PLUS_SIGN || codePoints[c] === HYPHEN_MINUS) { + if (codePoints[c] === HYPHEN_MINUS) { + sign = -1; + } + c++; + } + var integers = []; + while (isDigit(codePoints[c])) { + integers.push(codePoints[c++]); + } + var int = integers.length ? parseInt(fromCodePoint$1.apply(void 0, integers), 10) : 0; + if (codePoints[c] === FULL_STOP) { + c++; + } + var fraction = []; + while (isDigit(codePoints[c])) { + fraction.push(codePoints[c++]); + } + var fracd = fraction.length; + var frac = fracd ? parseInt(fromCodePoint$1.apply(void 0, fraction), 10) : 0; + if (codePoints[c] === E || codePoints[c] === e) { + c++; + } + var expsign = 1; + if (codePoints[c] === PLUS_SIGN || codePoints[c] === HYPHEN_MINUS) { + if (codePoints[c] === HYPHEN_MINUS) { + expsign = -1; + } + c++; + } + var exponent = []; + while (isDigit(codePoints[c])) { + exponent.push(codePoints[c++]); + } + var exp = exponent.length ? parseInt(fromCodePoint$1.apply(void 0, exponent), 10) : 0; + return sign * (int + frac * Math.pow(10, -fracd)) * Math.pow(10, expsign * exp); + }; + var LEFT_PARENTHESIS_TOKEN = { + type: 2 /* LEFT_PARENTHESIS_TOKEN */ + }; + var RIGHT_PARENTHESIS_TOKEN = { + type: 3 /* RIGHT_PARENTHESIS_TOKEN */ + }; + var COMMA_TOKEN = { type: 4 /* COMMA_TOKEN */ }; + var SUFFIX_MATCH_TOKEN = { type: 13 /* SUFFIX_MATCH_TOKEN */ }; + var PREFIX_MATCH_TOKEN = { type: 8 /* PREFIX_MATCH_TOKEN */ }; + var COLUMN_TOKEN = { type: 21 /* COLUMN_TOKEN */ }; + var DASH_MATCH_TOKEN = { type: 9 /* DASH_MATCH_TOKEN */ }; + var INCLUDE_MATCH_TOKEN = { type: 10 /* INCLUDE_MATCH_TOKEN */ }; + var LEFT_CURLY_BRACKET_TOKEN = { + type: 11 /* LEFT_CURLY_BRACKET_TOKEN */ + }; + var RIGHT_CURLY_BRACKET_TOKEN = { + type: 12 /* RIGHT_CURLY_BRACKET_TOKEN */ + }; + var SUBSTRING_MATCH_TOKEN = { type: 14 /* SUBSTRING_MATCH_TOKEN */ }; + var BAD_URL_TOKEN = { type: 23 /* BAD_URL_TOKEN */ }; + var BAD_STRING_TOKEN = { type: 1 /* BAD_STRING_TOKEN */ }; + var CDO_TOKEN = { type: 25 /* CDO_TOKEN */ }; + var CDC_TOKEN = { type: 24 /* CDC_TOKEN */ }; + var COLON_TOKEN = { type: 26 /* COLON_TOKEN */ }; + var SEMICOLON_TOKEN = { type: 27 /* SEMICOLON_TOKEN */ }; + var LEFT_SQUARE_BRACKET_TOKEN = { + type: 28 /* LEFT_SQUARE_BRACKET_TOKEN */ + }; + var RIGHT_SQUARE_BRACKET_TOKEN = { + type: 29 /* RIGHT_SQUARE_BRACKET_TOKEN */ + }; + var WHITESPACE_TOKEN = { type: 31 /* WHITESPACE_TOKEN */ }; + var EOF_TOKEN = { type: 32 /* EOF_TOKEN */ }; + var Tokenizer = /** @class */ (function () { + function Tokenizer() { + this._value = []; + } + Tokenizer.prototype.write = function (chunk) { + this._value = this._value.concat(toCodePoints$1(chunk)); + }; + Tokenizer.prototype.read = function () { + var tokens = []; + var token = this.consumeToken(); + while (token !== EOF_TOKEN) { + tokens.push(token); + token = this.consumeToken(); + } + return tokens; + }; + Tokenizer.prototype.consumeToken = function () { + var codePoint = this.consumeCodePoint(); + switch (codePoint) { + case QUOTATION_MARK: + return this.consumeStringToken(QUOTATION_MARK); + case NUMBER_SIGN: + var c1 = this.peekCodePoint(0); + var c2 = this.peekCodePoint(1); + var c3 = this.peekCodePoint(2); + if (isNameCodePoint(c1) || isValidEscape(c2, c3)) { + var flags = isIdentifierStart(c1, c2, c3) ? FLAG_ID : FLAG_UNRESTRICTED; + var value = this.consumeName(); + return { type: 5 /* HASH_TOKEN */, value: value, flags: flags }; + } + break; + case DOLLAR_SIGN: + if (this.peekCodePoint(0) === EQUALS_SIGN) { + this.consumeCodePoint(); + return SUFFIX_MATCH_TOKEN; + } + break; + case APOSTROPHE: + return this.consumeStringToken(APOSTROPHE); + case LEFT_PARENTHESIS: + return LEFT_PARENTHESIS_TOKEN; + case RIGHT_PARENTHESIS: + return RIGHT_PARENTHESIS_TOKEN; + case ASTERISK: + if (this.peekCodePoint(0) === EQUALS_SIGN) { + this.consumeCodePoint(); + return SUBSTRING_MATCH_TOKEN; + } + break; + case PLUS_SIGN: + if (isNumberStart(codePoint, this.peekCodePoint(0), this.peekCodePoint(1))) { + this.reconsumeCodePoint(codePoint); + return this.consumeNumericToken(); + } + break; + case COMMA: + return COMMA_TOKEN; + case HYPHEN_MINUS: + var e1 = codePoint; + var e2 = this.peekCodePoint(0); + var e3 = this.peekCodePoint(1); + if (isNumberStart(e1, e2, e3)) { + this.reconsumeCodePoint(codePoint); + return this.consumeNumericToken(); + } + if (isIdentifierStart(e1, e2, e3)) { + this.reconsumeCodePoint(codePoint); + return this.consumeIdentLikeToken(); + } + if (e2 === HYPHEN_MINUS && e3 === GREATER_THAN_SIGN) { + this.consumeCodePoint(); + this.consumeCodePoint(); + return CDC_TOKEN; + } + break; + case FULL_STOP: + if (isNumberStart(codePoint, this.peekCodePoint(0), this.peekCodePoint(1))) { + this.reconsumeCodePoint(codePoint); + return this.consumeNumericToken(); + } + break; + case SOLIDUS: + if (this.peekCodePoint(0) === ASTERISK) { + this.consumeCodePoint(); + while (true) { + var c = this.consumeCodePoint(); + if (c === ASTERISK) { + c = this.consumeCodePoint(); + if (c === SOLIDUS) { + return this.consumeToken(); + } + } + if (c === EOF) { + return this.consumeToken(); + } + } + } + break; + case COLON: + return COLON_TOKEN; + case SEMICOLON: + return SEMICOLON_TOKEN; + case LESS_THAN_SIGN: + if (this.peekCodePoint(0) === EXCLAMATION_MARK && + this.peekCodePoint(1) === HYPHEN_MINUS && + this.peekCodePoint(2) === HYPHEN_MINUS) { + this.consumeCodePoint(); + this.consumeCodePoint(); + return CDO_TOKEN; + } + break; + case COMMERCIAL_AT: + var a1 = this.peekCodePoint(0); + var a2 = this.peekCodePoint(1); + var a3 = this.peekCodePoint(2); + if (isIdentifierStart(a1, a2, a3)) { + var value = this.consumeName(); + return { type: 7 /* AT_KEYWORD_TOKEN */, value: value }; + } + break; + case LEFT_SQUARE_BRACKET: + return LEFT_SQUARE_BRACKET_TOKEN; + case REVERSE_SOLIDUS: + if (isValidEscape(codePoint, this.peekCodePoint(0))) { + this.reconsumeCodePoint(codePoint); + return this.consumeIdentLikeToken(); + } + break; + case RIGHT_SQUARE_BRACKET: + return RIGHT_SQUARE_BRACKET_TOKEN; + case CIRCUMFLEX_ACCENT: + if (this.peekCodePoint(0) === EQUALS_SIGN) { + this.consumeCodePoint(); + return PREFIX_MATCH_TOKEN; + } + break; + case LEFT_CURLY_BRACKET: + return LEFT_CURLY_BRACKET_TOKEN; + case RIGHT_CURLY_BRACKET: + return RIGHT_CURLY_BRACKET_TOKEN; + case u: + case U: + var u1 = this.peekCodePoint(0); + var u2 = this.peekCodePoint(1); + if (u1 === PLUS_SIGN && (isHex(u2) || u2 === QUESTION_MARK)) { + this.consumeCodePoint(); + this.consumeUnicodeRangeToken(); + } + this.reconsumeCodePoint(codePoint); + return this.consumeIdentLikeToken(); + case VERTICAL_LINE: + if (this.peekCodePoint(0) === EQUALS_SIGN) { + this.consumeCodePoint(); + return DASH_MATCH_TOKEN; + } + if (this.peekCodePoint(0) === VERTICAL_LINE) { + this.consumeCodePoint(); + return COLUMN_TOKEN; + } + break; + case TILDE: + if (this.peekCodePoint(0) === EQUALS_SIGN) { + this.consumeCodePoint(); + return INCLUDE_MATCH_TOKEN; + } + break; + case EOF: + return EOF_TOKEN; + } + if (isWhiteSpace(codePoint)) { + this.consumeWhiteSpace(); + return WHITESPACE_TOKEN; + } + if (isDigit(codePoint)) { + this.reconsumeCodePoint(codePoint); + return this.consumeNumericToken(); + } + if (isNameStartCodePoint(codePoint)) { + this.reconsumeCodePoint(codePoint); + return this.consumeIdentLikeToken(); + } + return { type: 6 /* DELIM_TOKEN */, value: fromCodePoint$1(codePoint) }; + }; + Tokenizer.prototype.consumeCodePoint = function () { + var value = this._value.shift(); + return typeof value === 'undefined' ? -1 : value; + }; + Tokenizer.prototype.reconsumeCodePoint = function (codePoint) { + this._value.unshift(codePoint); + }; + Tokenizer.prototype.peekCodePoint = function (delta) { + if (delta >= this._value.length) { + return -1; + } + return this._value[delta]; + }; + Tokenizer.prototype.consumeUnicodeRangeToken = function () { + var digits = []; + var codePoint = this.consumeCodePoint(); + while (isHex(codePoint) && digits.length < 6) { + digits.push(codePoint); + codePoint = this.consumeCodePoint(); + } + var questionMarks = false; + while (codePoint === QUESTION_MARK && digits.length < 6) { + digits.push(codePoint); + codePoint = this.consumeCodePoint(); + questionMarks = true; + } + if (questionMarks) { + var start_1 = parseInt(fromCodePoint$1.apply(void 0, digits.map(function (digit) { return (digit === QUESTION_MARK ? ZERO : digit); })), 16); + var end = parseInt(fromCodePoint$1.apply(void 0, digits.map(function (digit) { return (digit === QUESTION_MARK ? F : digit); })), 16); + return { type: 30 /* UNICODE_RANGE_TOKEN */, start: start_1, end: end }; + } + var start = parseInt(fromCodePoint$1.apply(void 0, digits), 16); + if (this.peekCodePoint(0) === HYPHEN_MINUS && isHex(this.peekCodePoint(1))) { + this.consumeCodePoint(); + codePoint = this.consumeCodePoint(); + var endDigits = []; + while (isHex(codePoint) && endDigits.length < 6) { + endDigits.push(codePoint); + codePoint = this.consumeCodePoint(); + } + var end = parseInt(fromCodePoint$1.apply(void 0, endDigits), 16); + return { type: 30 /* UNICODE_RANGE_TOKEN */, start: start, end: end }; + } + else { + return { type: 30 /* UNICODE_RANGE_TOKEN */, start: start, end: start }; + } + }; + Tokenizer.prototype.consumeIdentLikeToken = function () { + var value = this.consumeName(); + if (value.toLowerCase() === 'url' && this.peekCodePoint(0) === LEFT_PARENTHESIS) { + this.consumeCodePoint(); + return this.consumeUrlToken(); + } + else if (this.peekCodePoint(0) === LEFT_PARENTHESIS) { + this.consumeCodePoint(); + return { type: 19 /* FUNCTION_TOKEN */, value: value }; + } + return { type: 20 /* IDENT_TOKEN */, value: value }; + }; + Tokenizer.prototype.consumeUrlToken = function () { + var value = []; + this.consumeWhiteSpace(); + if (this.peekCodePoint(0) === EOF) { + return { type: 22 /* URL_TOKEN */, value: '' }; + } + var next = this.peekCodePoint(0); + if (next === APOSTROPHE || next === QUOTATION_MARK) { + var stringToken = this.consumeStringToken(this.consumeCodePoint()); + if (stringToken.type === 0 /* STRING_TOKEN */) { + this.consumeWhiteSpace(); + if (this.peekCodePoint(0) === EOF || this.peekCodePoint(0) === RIGHT_PARENTHESIS) { + this.consumeCodePoint(); + return { type: 22 /* URL_TOKEN */, value: stringToken.value }; + } + } + this.consumeBadUrlRemnants(); + return BAD_URL_TOKEN; + } + while (true) { + var codePoint = this.consumeCodePoint(); + if (codePoint === EOF || codePoint === RIGHT_PARENTHESIS) { + return { type: 22 /* URL_TOKEN */, value: fromCodePoint$1.apply(void 0, value) }; + } + else if (isWhiteSpace(codePoint)) { + this.consumeWhiteSpace(); + if (this.peekCodePoint(0) === EOF || this.peekCodePoint(0) === RIGHT_PARENTHESIS) { + this.consumeCodePoint(); + return { type: 22 /* URL_TOKEN */, value: fromCodePoint$1.apply(void 0, value) }; + } + this.consumeBadUrlRemnants(); + return BAD_URL_TOKEN; + } + else if (codePoint === QUOTATION_MARK || + codePoint === APOSTROPHE || + codePoint === LEFT_PARENTHESIS || + isNonPrintableCodePoint(codePoint)) { + this.consumeBadUrlRemnants(); + return BAD_URL_TOKEN; + } + else if (codePoint === REVERSE_SOLIDUS) { + if (isValidEscape(codePoint, this.peekCodePoint(0))) { + value.push(this.consumeEscapedCodePoint()); + } + else { + this.consumeBadUrlRemnants(); + return BAD_URL_TOKEN; + } + } + else { + value.push(codePoint); + } + } + }; + Tokenizer.prototype.consumeWhiteSpace = function () { + while (isWhiteSpace(this.peekCodePoint(0))) { + this.consumeCodePoint(); + } + }; + Tokenizer.prototype.consumeBadUrlRemnants = function () { + while (true) { + var codePoint = this.consumeCodePoint(); + if (codePoint === RIGHT_PARENTHESIS || codePoint === EOF) { + return; + } + if (isValidEscape(codePoint, this.peekCodePoint(0))) { + this.consumeEscapedCodePoint(); + } + } + }; + Tokenizer.prototype.consumeStringSlice = function (count) { + var SLICE_STACK_SIZE = 50000; + var value = ''; + while (count > 0) { + var amount = Math.min(SLICE_STACK_SIZE, count); + value += fromCodePoint$1.apply(void 0, this._value.splice(0, amount)); + count -= amount; + } + this._value.shift(); + return value; + }; + Tokenizer.prototype.consumeStringToken = function (endingCodePoint) { + var value = ''; + var i = 0; + do { + var codePoint = this._value[i]; + if (codePoint === EOF || codePoint === undefined || codePoint === endingCodePoint) { + value += this.consumeStringSlice(i); + return { type: 0 /* STRING_TOKEN */, value: value }; + } + if (codePoint === LINE_FEED) { + this._value.splice(0, i); + return BAD_STRING_TOKEN; + } + if (codePoint === REVERSE_SOLIDUS) { + var next = this._value[i + 1]; + if (next !== EOF && next !== undefined) { + if (next === LINE_FEED) { + value += this.consumeStringSlice(i); + i = -1; + this._value.shift(); + } + else if (isValidEscape(codePoint, next)) { + value += this.consumeStringSlice(i); + value += fromCodePoint$1(this.consumeEscapedCodePoint()); + i = -1; + } + } + } + i++; + } while (true); + }; + Tokenizer.prototype.consumeNumber = function () { + var repr = []; + var type = FLAG_INTEGER; + var c1 = this.peekCodePoint(0); + if (c1 === PLUS_SIGN || c1 === HYPHEN_MINUS) { + repr.push(this.consumeCodePoint()); + } + while (isDigit(this.peekCodePoint(0))) { + repr.push(this.consumeCodePoint()); + } + c1 = this.peekCodePoint(0); + var c2 = this.peekCodePoint(1); + if (c1 === FULL_STOP && isDigit(c2)) { + repr.push(this.consumeCodePoint(), this.consumeCodePoint()); + type = FLAG_NUMBER; + while (isDigit(this.peekCodePoint(0))) { + repr.push(this.consumeCodePoint()); + } + } + c1 = this.peekCodePoint(0); + c2 = this.peekCodePoint(1); + var c3 = this.peekCodePoint(2); + if ((c1 === E || c1 === e) && (((c2 === PLUS_SIGN || c2 === HYPHEN_MINUS) && isDigit(c3)) || isDigit(c2))) { + repr.push(this.consumeCodePoint(), this.consumeCodePoint()); + type = FLAG_NUMBER; + while (isDigit(this.peekCodePoint(0))) { + repr.push(this.consumeCodePoint()); + } + } + return [stringToNumber(repr), type]; + }; + Tokenizer.prototype.consumeNumericToken = function () { + var _a = this.consumeNumber(), number = _a[0], flags = _a[1]; + var c1 = this.peekCodePoint(0); + var c2 = this.peekCodePoint(1); + var c3 = this.peekCodePoint(2); + if (isIdentifierStart(c1, c2, c3)) { + var unit = this.consumeName(); + return { type: 15 /* DIMENSION_TOKEN */, number: number, flags: flags, unit: unit }; + } + if (c1 === PERCENTAGE_SIGN) { + this.consumeCodePoint(); + return { type: 16 /* PERCENTAGE_TOKEN */, number: number, flags: flags }; + } + return { type: 17 /* NUMBER_TOKEN */, number: number, flags: flags }; + }; + Tokenizer.prototype.consumeEscapedCodePoint = function () { + var codePoint = this.consumeCodePoint(); + if (isHex(codePoint)) { + var hex = fromCodePoint$1(codePoint); + while (isHex(this.peekCodePoint(0)) && hex.length < 6) { + hex += fromCodePoint$1(this.consumeCodePoint()); + } + if (isWhiteSpace(this.peekCodePoint(0))) { + this.consumeCodePoint(); + } + var hexCodePoint = parseInt(hex, 16); + if (hexCodePoint === 0 || isSurrogateCodePoint(hexCodePoint) || hexCodePoint > 0x10ffff) { + return REPLACEMENT_CHARACTER; + } + return hexCodePoint; + } + if (codePoint === EOF) { + return REPLACEMENT_CHARACTER; + } + return codePoint; + }; + Tokenizer.prototype.consumeName = function () { + var result = ''; + while (true) { + var codePoint = this.consumeCodePoint(); + if (isNameCodePoint(codePoint)) { + result += fromCodePoint$1(codePoint); + } + else if (isValidEscape(codePoint, this.peekCodePoint(0))) { + result += fromCodePoint$1(this.consumeEscapedCodePoint()); + } + else { + this.reconsumeCodePoint(codePoint); + return result; + } + } + }; + return Tokenizer; + }()); + + var Parser = /** @class */ (function () { + function Parser(tokens) { + this._tokens = tokens; + } + Parser.create = function (value) { + var tokenizer = new Tokenizer(); + tokenizer.write(value); + return new Parser(tokenizer.read()); + }; + Parser.parseValue = function (value) { + return Parser.create(value).parseComponentValue(); + }; + Parser.parseValues = function (value) { + return Parser.create(value).parseComponentValues(); + }; + Parser.prototype.parseComponentValue = function () { + var token = this.consumeToken(); + while (token.type === 31 /* WHITESPACE_TOKEN */) { + token = this.consumeToken(); + } + if (token.type === 32 /* EOF_TOKEN */) { + throw new SyntaxError("Error parsing CSS component value, unexpected EOF"); + } + this.reconsumeToken(token); + var value = this.consumeComponentValue(); + do { + token = this.consumeToken(); + } while (token.type === 31 /* WHITESPACE_TOKEN */); + if (token.type === 32 /* EOF_TOKEN */) { + return value; + } + throw new SyntaxError("Error parsing CSS component value, multiple values found when expecting only one"); + }; + Parser.prototype.parseComponentValues = function () { + var values = []; + while (true) { + var value = this.consumeComponentValue(); + if (value.type === 32 /* EOF_TOKEN */) { + return values; + } + values.push(value); + values.push(); + } + }; + Parser.prototype.consumeComponentValue = function () { + var token = this.consumeToken(); + switch (token.type) { + case 11 /* LEFT_CURLY_BRACKET_TOKEN */: + case 28 /* LEFT_SQUARE_BRACKET_TOKEN */: + case 2 /* LEFT_PARENTHESIS_TOKEN */: + return this.consumeSimpleBlock(token.type); + case 19 /* FUNCTION_TOKEN */: + return this.consumeFunction(token); + } + return token; + }; + Parser.prototype.consumeSimpleBlock = function (type) { + var block = { type: type, values: [] }; + var token = this.consumeToken(); + while (true) { + if (token.type === 32 /* EOF_TOKEN */ || isEndingTokenFor(token, type)) { + return block; + } + this.reconsumeToken(token); + block.values.push(this.consumeComponentValue()); + token = this.consumeToken(); + } + }; + Parser.prototype.consumeFunction = function (functionToken) { + var cssFunction = { + name: functionToken.value, + values: [], + type: 18 /* FUNCTION */ + }; + while (true) { + var token = this.consumeToken(); + if (token.type === 32 /* EOF_TOKEN */ || token.type === 3 /* RIGHT_PARENTHESIS_TOKEN */) { + return cssFunction; + } + this.reconsumeToken(token); + cssFunction.values.push(this.consumeComponentValue()); + } + }; + Parser.prototype.consumeToken = function () { + var token = this._tokens.shift(); + return typeof token === 'undefined' ? EOF_TOKEN : token; + }; + Parser.prototype.reconsumeToken = function (token) { + this._tokens.unshift(token); + }; + return Parser; + }()); + var isDimensionToken = function (token) { return token.type === 15 /* DIMENSION_TOKEN */; }; + var isNumberToken = function (token) { return token.type === 17 /* NUMBER_TOKEN */; }; + var isIdentToken = function (token) { return token.type === 20 /* IDENT_TOKEN */; }; + var isStringToken = function (token) { return token.type === 0 /* STRING_TOKEN */; }; + var isIdentWithValue = function (token, value) { + return isIdentToken(token) && token.value === value; + }; + var nonWhiteSpace = function (token) { return token.type !== 31 /* WHITESPACE_TOKEN */; }; + var nonFunctionArgSeparator = function (token) { + return token.type !== 31 /* WHITESPACE_TOKEN */ && token.type !== 4 /* COMMA_TOKEN */; + }; + var parseFunctionArgs = function (tokens) { + var args = []; + var arg = []; + tokens.forEach(function (token) { + if (token.type === 4 /* COMMA_TOKEN */) { + if (arg.length === 0) { + throw new Error("Error parsing function args, zero tokens for arg"); + } + args.push(arg); + arg = []; + return; + } + if (token.type !== 31 /* WHITESPACE_TOKEN */) { + arg.push(token); + } + }); + if (arg.length) { + args.push(arg); + } + return args; + }; + var isEndingTokenFor = function (token, type) { + if (type === 11 /* LEFT_CURLY_BRACKET_TOKEN */ && token.type === 12 /* RIGHT_CURLY_BRACKET_TOKEN */) { + return true; + } + if (type === 28 /* LEFT_SQUARE_BRACKET_TOKEN */ && token.type === 29 /* RIGHT_SQUARE_BRACKET_TOKEN */) { + return true; + } + return type === 2 /* LEFT_PARENTHESIS_TOKEN */ && token.type === 3 /* RIGHT_PARENTHESIS_TOKEN */; + }; + + var isLength = function (token) { + return token.type === 17 /* NUMBER_TOKEN */ || token.type === 15 /* DIMENSION_TOKEN */; + }; + + var isLengthPercentage = function (token) { + return token.type === 16 /* PERCENTAGE_TOKEN */ || isLength(token); + }; + var parseLengthPercentageTuple = function (tokens) { + return tokens.length > 1 ? [tokens[0], tokens[1]] : [tokens[0]]; + }; + var ZERO_LENGTH = { + type: 17 /* NUMBER_TOKEN */, + number: 0, + flags: FLAG_INTEGER + }; + var FIFTY_PERCENT = { + type: 16 /* PERCENTAGE_TOKEN */, + number: 50, + flags: FLAG_INTEGER + }; + var HUNDRED_PERCENT = { + type: 16 /* PERCENTAGE_TOKEN */, + number: 100, + flags: FLAG_INTEGER + }; + var getAbsoluteValueForTuple = function (tuple, width, height) { + var x = tuple[0], y = tuple[1]; + return [getAbsoluteValue(x, width), getAbsoluteValue(typeof y !== 'undefined' ? y : x, height)]; + }; + var getAbsoluteValue = function (token, parent) { + if (token.type === 16 /* PERCENTAGE_TOKEN */) { + return (token.number / 100) * parent; + } + if (isDimensionToken(token)) { + switch (token.unit) { + case 'rem': + case 'em': + return 16 * token.number; // TODO use correct font-size + case 'px': + default: + return token.number; + } + } + return token.number; + }; + + var DEG = 'deg'; + var GRAD = 'grad'; + var RAD = 'rad'; + var TURN = 'turn'; + var angle = { + name: 'angle', + parse: function (_context, value) { + if (value.type === 15 /* DIMENSION_TOKEN */) { + switch (value.unit) { + case DEG: + return (Math.PI * value.number) / 180; + case GRAD: + return (Math.PI / 200) * value.number; + case RAD: + return value.number; + case TURN: + return Math.PI * 2 * value.number; + } + } + throw new Error("Unsupported angle type"); + } + }; + var isAngle = function (value) { + if (value.type === 15 /* DIMENSION_TOKEN */) { + if (value.unit === DEG || value.unit === GRAD || value.unit === RAD || value.unit === TURN) { + return true; + } + } + return false; + }; + var parseNamedSide = function (tokens) { + var sideOrCorner = tokens + .filter(isIdentToken) + .map(function (ident) { return ident.value; }) + .join(' '); + switch (sideOrCorner) { + case 'to bottom right': + case 'to right bottom': + case 'left top': + case 'top left': + return [ZERO_LENGTH, ZERO_LENGTH]; + case 'to top': + case 'bottom': + return deg(0); + case 'to bottom left': + case 'to left bottom': + case 'right top': + case 'top right': + return [ZERO_LENGTH, HUNDRED_PERCENT]; + case 'to right': + case 'left': + return deg(90); + case 'to top left': + case 'to left top': + case 'right bottom': + case 'bottom right': + return [HUNDRED_PERCENT, HUNDRED_PERCENT]; + case 'to bottom': + case 'top': + return deg(180); + case 'to top right': + case 'to right top': + case 'left bottom': + case 'bottom left': + return [HUNDRED_PERCENT, ZERO_LENGTH]; + case 'to left': + case 'right': + return deg(270); + } + return 0; + }; + var deg = function (deg) { return (Math.PI * deg) / 180; }; + + var color$1 = { + name: 'color', + parse: function (context, value) { + if (value.type === 18 /* FUNCTION */) { + var colorFunction = SUPPORTED_COLOR_FUNCTIONS[value.name]; + if (typeof colorFunction === 'undefined') { + throw new Error("Attempting to parse an unsupported color function \"" + value.name + "\""); + } + return colorFunction(context, value.values); + } + if (value.type === 5 /* HASH_TOKEN */) { + if (value.value.length === 3) { + var r = value.value.substring(0, 1); + var g = value.value.substring(1, 2); + var b = value.value.substring(2, 3); + return pack(parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16), 1); + } + if (value.value.length === 4) { + var r = value.value.substring(0, 1); + var g = value.value.substring(1, 2); + var b = value.value.substring(2, 3); + var a = value.value.substring(3, 4); + return pack(parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16), parseInt(a + a, 16) / 255); + } + if (value.value.length === 6) { + var r = value.value.substring(0, 2); + var g = value.value.substring(2, 4); + var b = value.value.substring(4, 6); + return pack(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), 1); + } + if (value.value.length === 8) { + var r = value.value.substring(0, 2); + var g = value.value.substring(2, 4); + var b = value.value.substring(4, 6); + var a = value.value.substring(6, 8); + return pack(parseInt(r, 16), parseInt(g, 16), parseInt(b, 16), parseInt(a, 16) / 255); + } + } + if (value.type === 20 /* IDENT_TOKEN */) { + var namedColor = COLORS[value.value.toUpperCase()]; + if (typeof namedColor !== 'undefined') { + return namedColor; + } + } + return COLORS.TRANSPARENT; + } + }; + var isTransparent = function (color) { return (0xff & color) === 0; }; + var asString = function (color) { + var alpha = 0xff & color; + var blue = 0xff & (color >> 8); + var green = 0xff & (color >> 16); + var red = 0xff & (color >> 24); + return alpha < 255 ? "rgba(" + red + "," + green + "," + blue + "," + alpha / 255 + ")" : "rgb(" + red + "," + green + "," + blue + ")"; + }; + var pack = function (r, g, b, a) { + return ((r << 24) | (g << 16) | (b << 8) | (Math.round(a * 255) << 0)) >>> 0; + }; + var getTokenColorValue = function (token, i) { + if (token.type === 17 /* NUMBER_TOKEN */) { + return token.number; + } + if (token.type === 16 /* PERCENTAGE_TOKEN */) { + var max = i === 3 ? 1 : 255; + return i === 3 ? (token.number / 100) * max : Math.round((token.number / 100) * max); + } + return 0; + }; + var rgb = function (_context, args) { + var tokens = args.filter(nonFunctionArgSeparator); + if (tokens.length === 3) { + var _a = tokens.map(getTokenColorValue), r = _a[0], g = _a[1], b = _a[2]; + return pack(r, g, b, 1); + } + if (tokens.length === 4) { + var _b = tokens.map(getTokenColorValue), r = _b[0], g = _b[1], b = _b[2], a = _b[3]; + return pack(r, g, b, a); + } + return 0; + }; + function hue2rgb(t1, t2, hue) { + if (hue < 0) { + hue += 1; + } + if (hue >= 1) { + hue -= 1; + } + if (hue < 1 / 6) { + return (t2 - t1) * hue * 6 + t1; + } + else if (hue < 1 / 2) { + return t2; + } + else if (hue < 2 / 3) { + return (t2 - t1) * 6 * (2 / 3 - hue) + t1; + } + else { + return t1; + } + } + var hsl = function (context, args) { + var tokens = args.filter(nonFunctionArgSeparator); + var hue = tokens[0], saturation = tokens[1], lightness = tokens[2], alpha = tokens[3]; + var h = (hue.type === 17 /* NUMBER_TOKEN */ ? deg(hue.number) : angle.parse(context, hue)) / (Math.PI * 2); + var s = isLengthPercentage(saturation) ? saturation.number / 100 : 0; + var l = isLengthPercentage(lightness) ? lightness.number / 100 : 0; + var a = typeof alpha !== 'undefined' && isLengthPercentage(alpha) ? getAbsoluteValue(alpha, 1) : 1; + if (s === 0) { + return pack(l * 255, l * 255, l * 255, 1); + } + var t2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; + var t1 = l * 2 - t2; + var r = hue2rgb(t1, t2, h + 1 / 3); + var g = hue2rgb(t1, t2, h); + var b = hue2rgb(t1, t2, h - 1 / 3); + return pack(r * 255, g * 255, b * 255, a); + }; + var SUPPORTED_COLOR_FUNCTIONS = { + hsl: hsl, + hsla: hsl, + rgb: rgb, + rgba: rgb + }; + var parseColor = function (context, value) { + return color$1.parse(context, Parser.create(value).parseComponentValue()); + }; + var COLORS = { + ALICEBLUE: 0xf0f8ffff, + ANTIQUEWHITE: 0xfaebd7ff, + AQUA: 0x00ffffff, + AQUAMARINE: 0x7fffd4ff, + AZURE: 0xf0ffffff, + BEIGE: 0xf5f5dcff, + BISQUE: 0xffe4c4ff, + BLACK: 0x000000ff, + BLANCHEDALMOND: 0xffebcdff, + BLUE: 0x0000ffff, + BLUEVIOLET: 0x8a2be2ff, + BROWN: 0xa52a2aff, + BURLYWOOD: 0xdeb887ff, + CADETBLUE: 0x5f9ea0ff, + CHARTREUSE: 0x7fff00ff, + CHOCOLATE: 0xd2691eff, + CORAL: 0xff7f50ff, + CORNFLOWERBLUE: 0x6495edff, + CORNSILK: 0xfff8dcff, + CRIMSON: 0xdc143cff, + CYAN: 0x00ffffff, + DARKBLUE: 0x00008bff, + DARKCYAN: 0x008b8bff, + DARKGOLDENROD: 0xb886bbff, + DARKGRAY: 0xa9a9a9ff, + DARKGREEN: 0x006400ff, + DARKGREY: 0xa9a9a9ff, + DARKKHAKI: 0xbdb76bff, + DARKMAGENTA: 0x8b008bff, + DARKOLIVEGREEN: 0x556b2fff, + DARKORANGE: 0xff8c00ff, + DARKORCHID: 0x9932ccff, + DARKRED: 0x8b0000ff, + DARKSALMON: 0xe9967aff, + DARKSEAGREEN: 0x8fbc8fff, + DARKSLATEBLUE: 0x483d8bff, + DARKSLATEGRAY: 0x2f4f4fff, + DARKSLATEGREY: 0x2f4f4fff, + DARKTURQUOISE: 0x00ced1ff, + DARKVIOLET: 0x9400d3ff, + DEEPPINK: 0xff1493ff, + DEEPSKYBLUE: 0x00bfffff, + DIMGRAY: 0x696969ff, + DIMGREY: 0x696969ff, + DODGERBLUE: 0x1e90ffff, + FIREBRICK: 0xb22222ff, + FLORALWHITE: 0xfffaf0ff, + FORESTGREEN: 0x228b22ff, + FUCHSIA: 0xff00ffff, + GAINSBORO: 0xdcdcdcff, + GHOSTWHITE: 0xf8f8ffff, + GOLD: 0xffd700ff, + GOLDENROD: 0xdaa520ff, + GRAY: 0x808080ff, + GREEN: 0x008000ff, + GREENYELLOW: 0xadff2fff, + GREY: 0x808080ff, + HONEYDEW: 0xf0fff0ff, + HOTPINK: 0xff69b4ff, + INDIANRED: 0xcd5c5cff, + INDIGO: 0x4b0082ff, + IVORY: 0xfffff0ff, + KHAKI: 0xf0e68cff, + LAVENDER: 0xe6e6faff, + LAVENDERBLUSH: 0xfff0f5ff, + LAWNGREEN: 0x7cfc00ff, + LEMONCHIFFON: 0xfffacdff, + LIGHTBLUE: 0xadd8e6ff, + LIGHTCORAL: 0xf08080ff, + LIGHTCYAN: 0xe0ffffff, + LIGHTGOLDENRODYELLOW: 0xfafad2ff, + LIGHTGRAY: 0xd3d3d3ff, + LIGHTGREEN: 0x90ee90ff, + LIGHTGREY: 0xd3d3d3ff, + LIGHTPINK: 0xffb6c1ff, + LIGHTSALMON: 0xffa07aff, + LIGHTSEAGREEN: 0x20b2aaff, + LIGHTSKYBLUE: 0x87cefaff, + LIGHTSLATEGRAY: 0x778899ff, + LIGHTSLATEGREY: 0x778899ff, + LIGHTSTEELBLUE: 0xb0c4deff, + LIGHTYELLOW: 0xffffe0ff, + LIME: 0x00ff00ff, + LIMEGREEN: 0x32cd32ff, + LINEN: 0xfaf0e6ff, + MAGENTA: 0xff00ffff, + MAROON: 0x800000ff, + MEDIUMAQUAMARINE: 0x66cdaaff, + MEDIUMBLUE: 0x0000cdff, + MEDIUMORCHID: 0xba55d3ff, + MEDIUMPURPLE: 0x9370dbff, + MEDIUMSEAGREEN: 0x3cb371ff, + MEDIUMSLATEBLUE: 0x7b68eeff, + MEDIUMSPRINGGREEN: 0x00fa9aff, + MEDIUMTURQUOISE: 0x48d1ccff, + MEDIUMVIOLETRED: 0xc71585ff, + MIDNIGHTBLUE: 0x191970ff, + MINTCREAM: 0xf5fffaff, + MISTYROSE: 0xffe4e1ff, + MOCCASIN: 0xffe4b5ff, + NAVAJOWHITE: 0xffdeadff, + NAVY: 0x000080ff, + OLDLACE: 0xfdf5e6ff, + OLIVE: 0x808000ff, + OLIVEDRAB: 0x6b8e23ff, + ORANGE: 0xffa500ff, + ORANGERED: 0xff4500ff, + ORCHID: 0xda70d6ff, + PALEGOLDENROD: 0xeee8aaff, + PALEGREEN: 0x98fb98ff, + PALETURQUOISE: 0xafeeeeff, + PALEVIOLETRED: 0xdb7093ff, + PAPAYAWHIP: 0xffefd5ff, + PEACHPUFF: 0xffdab9ff, + PERU: 0xcd853fff, + PINK: 0xffc0cbff, + PLUM: 0xdda0ddff, + POWDERBLUE: 0xb0e0e6ff, + PURPLE: 0x800080ff, + REBECCAPURPLE: 0x663399ff, + RED: 0xff0000ff, + ROSYBROWN: 0xbc8f8fff, + ROYALBLUE: 0x4169e1ff, + SADDLEBROWN: 0x8b4513ff, + SALMON: 0xfa8072ff, + SANDYBROWN: 0xf4a460ff, + SEAGREEN: 0x2e8b57ff, + SEASHELL: 0xfff5eeff, + SIENNA: 0xa0522dff, + SILVER: 0xc0c0c0ff, + SKYBLUE: 0x87ceebff, + SLATEBLUE: 0x6a5acdff, + SLATEGRAY: 0x708090ff, + SLATEGREY: 0x708090ff, + SNOW: 0xfffafaff, + SPRINGGREEN: 0x00ff7fff, + STEELBLUE: 0x4682b4ff, + TAN: 0xd2b48cff, + TEAL: 0x008080ff, + THISTLE: 0xd8bfd8ff, + TOMATO: 0xff6347ff, + TRANSPARENT: 0x00000000, + TURQUOISE: 0x40e0d0ff, + VIOLET: 0xee82eeff, + WHEAT: 0xf5deb3ff, + WHITE: 0xffffffff, + WHITESMOKE: 0xf5f5f5ff, + YELLOW: 0xffff00ff, + YELLOWGREEN: 0x9acd32ff + }; + + var backgroundClip = { + name: 'background-clip', + initialValue: 'border-box', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return tokens.map(function (token) { + if (isIdentToken(token)) { + switch (token.value) { + case 'padding-box': + return 1 /* PADDING_BOX */; + case 'content-box': + return 2 /* CONTENT_BOX */; + } + } + return 0 /* BORDER_BOX */; + }); + } + }; + + var backgroundColor = { + name: "background-color", + initialValue: 'transparent', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'color' + }; + + var parseColorStop = function (context, args) { + var color = color$1.parse(context, args[0]); + var stop = args[1]; + return stop && isLengthPercentage(stop) ? { color: color, stop: stop } : { color: color, stop: null }; + }; + var processColorStops = function (stops, lineLength) { + var first = stops[0]; + var last = stops[stops.length - 1]; + if (first.stop === null) { + first.stop = ZERO_LENGTH; + } + if (last.stop === null) { + last.stop = HUNDRED_PERCENT; + } + var processStops = []; + var previous = 0; + for (var i = 0; i < stops.length; i++) { + var stop_1 = stops[i].stop; + if (stop_1 !== null) { + var absoluteValue = getAbsoluteValue(stop_1, lineLength); + if (absoluteValue > previous) { + processStops.push(absoluteValue); + } + else { + processStops.push(previous); + } + previous = absoluteValue; + } + else { + processStops.push(null); + } + } + var gapBegin = null; + for (var i = 0; i < processStops.length; i++) { + var stop_2 = processStops[i]; + if (stop_2 === null) { + if (gapBegin === null) { + gapBegin = i; + } + } + else if (gapBegin !== null) { + var gapLength = i - gapBegin; + var beforeGap = processStops[gapBegin - 1]; + var gapValue = (stop_2 - beforeGap) / (gapLength + 1); + for (var g = 1; g <= gapLength; g++) { + processStops[gapBegin + g - 1] = gapValue * g; + } + gapBegin = null; + } + } + return stops.map(function (_a, i) { + var color = _a.color; + return { color: color, stop: Math.max(Math.min(1, processStops[i] / lineLength), 0) }; + }); + }; + var getAngleFromCorner = function (corner, width, height) { + var centerX = width / 2; + var centerY = height / 2; + var x = getAbsoluteValue(corner[0], width) - centerX; + var y = centerY - getAbsoluteValue(corner[1], height); + return (Math.atan2(y, x) + Math.PI * 2) % (Math.PI * 2); + }; + var calculateGradientDirection = function (angle, width, height) { + var radian = typeof angle === 'number' ? angle : getAngleFromCorner(angle, width, height); + var lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian)); + var halfWidth = width / 2; + var halfHeight = height / 2; + var halfLineLength = lineLength / 2; + var yDiff = Math.sin(radian - Math.PI / 2) * halfLineLength; + var xDiff = Math.cos(radian - Math.PI / 2) * halfLineLength; + return [lineLength, halfWidth - xDiff, halfWidth + xDiff, halfHeight - yDiff, halfHeight + yDiff]; + }; + var distance = function (a, b) { return Math.sqrt(a * a + b * b); }; + var findCorner = function (width, height, x, y, closest) { + var corners = [ + [0, 0], + [0, height], + [width, 0], + [width, height] + ]; + return corners.reduce(function (stat, corner) { + var cx = corner[0], cy = corner[1]; + var d = distance(x - cx, y - cy); + if (closest ? d < stat.optimumDistance : d > stat.optimumDistance) { + return { + optimumCorner: corner, + optimumDistance: d + }; + } + return stat; + }, { + optimumDistance: closest ? Infinity : -Infinity, + optimumCorner: null + }).optimumCorner; + }; + var calculateRadius = function (gradient, x, y, width, height) { + var rx = 0; + var ry = 0; + switch (gradient.size) { + case 0 /* CLOSEST_SIDE */: + // The ending shape is sized so that that it exactly meets the side of the gradient box closest to the gradient’s center. + // If the shape is an ellipse, it exactly meets the closest side in each dimension. + if (gradient.shape === 0 /* CIRCLE */) { + rx = ry = Math.min(Math.abs(x), Math.abs(x - width), Math.abs(y), Math.abs(y - height)); + } + else if (gradient.shape === 1 /* ELLIPSE */) { + rx = Math.min(Math.abs(x), Math.abs(x - width)); + ry = Math.min(Math.abs(y), Math.abs(y - height)); + } + break; + case 2 /* CLOSEST_CORNER */: + // The ending shape is sized so that that it passes through the corner of the gradient box closest to the gradient’s center. + // If the shape is an ellipse, the ending shape is given the same aspect-ratio it would have if closest-side were specified. + if (gradient.shape === 0 /* CIRCLE */) { + rx = ry = Math.min(distance(x, y), distance(x, y - height), distance(x - width, y), distance(x - width, y - height)); + } + else if (gradient.shape === 1 /* ELLIPSE */) { + // Compute the ratio ry/rx (which is to be the same as for "closest-side") + var c = Math.min(Math.abs(y), Math.abs(y - height)) / Math.min(Math.abs(x), Math.abs(x - width)); + var _a = findCorner(width, height, x, y, true), cx = _a[0], cy = _a[1]; + rx = distance(cx - x, (cy - y) / c); + ry = c * rx; + } + break; + case 1 /* FARTHEST_SIDE */: + // Same as closest-side, except the ending shape is sized based on the farthest side(s) + if (gradient.shape === 0 /* CIRCLE */) { + rx = ry = Math.max(Math.abs(x), Math.abs(x - width), Math.abs(y), Math.abs(y - height)); + } + else if (gradient.shape === 1 /* ELLIPSE */) { + rx = Math.max(Math.abs(x), Math.abs(x - width)); + ry = Math.max(Math.abs(y), Math.abs(y - height)); + } + break; + case 3 /* FARTHEST_CORNER */: + // Same as closest-corner, except the ending shape is sized based on the farthest corner. + // If the shape is an ellipse, the ending shape is given the same aspect ratio it would have if farthest-side were specified. + if (gradient.shape === 0 /* CIRCLE */) { + rx = ry = Math.max(distance(x, y), distance(x, y - height), distance(x - width, y), distance(x - width, y - height)); + } + else if (gradient.shape === 1 /* ELLIPSE */) { + // Compute the ratio ry/rx (which is to be the same as for "farthest-side") + var c = Math.max(Math.abs(y), Math.abs(y - height)) / Math.max(Math.abs(x), Math.abs(x - width)); + var _b = findCorner(width, height, x, y, false), cx = _b[0], cy = _b[1]; + rx = distance(cx - x, (cy - y) / c); + ry = c * rx; + } + break; + } + if (Array.isArray(gradient.size)) { + rx = getAbsoluteValue(gradient.size[0], width); + ry = gradient.size.length === 2 ? getAbsoluteValue(gradient.size[1], height) : rx; + } + return [rx, ry]; + }; + + var linearGradient = function (context, tokens) { + var angle$1 = deg(180); + var stops = []; + parseFunctionArgs(tokens).forEach(function (arg, i) { + if (i === 0) { + var firstToken = arg[0]; + if (firstToken.type === 20 /* IDENT_TOKEN */ && firstToken.value === 'to') { + angle$1 = parseNamedSide(arg); + return; + } + else if (isAngle(firstToken)) { + angle$1 = angle.parse(context, firstToken); + return; + } + } + var colorStop = parseColorStop(context, arg); + stops.push(colorStop); + }); + return { angle: angle$1, stops: stops, type: 1 /* LINEAR_GRADIENT */ }; + }; + + var prefixLinearGradient = function (context, tokens) { + var angle$1 = deg(180); + var stops = []; + parseFunctionArgs(tokens).forEach(function (arg, i) { + if (i === 0) { + var firstToken = arg[0]; + if (firstToken.type === 20 /* IDENT_TOKEN */ && + ['top', 'left', 'right', 'bottom'].indexOf(firstToken.value) !== -1) { + angle$1 = parseNamedSide(arg); + return; + } + else if (isAngle(firstToken)) { + angle$1 = (angle.parse(context, firstToken) + deg(270)) % deg(360); + return; + } + } + var colorStop = parseColorStop(context, arg); + stops.push(colorStop); + }); + return { + angle: angle$1, + stops: stops, + type: 1 /* LINEAR_GRADIENT */ + }; + }; + + var webkitGradient = function (context, tokens) { + var angle = deg(180); + var stops = []; + var type = 1 /* LINEAR_GRADIENT */; + var shape = 0 /* CIRCLE */; + var size = 3 /* FARTHEST_CORNER */; + var position = []; + parseFunctionArgs(tokens).forEach(function (arg, i) { + var firstToken = arg[0]; + if (i === 0) { + if (isIdentToken(firstToken) && firstToken.value === 'linear') { + type = 1 /* LINEAR_GRADIENT */; + return; + } + else if (isIdentToken(firstToken) && firstToken.value === 'radial') { + type = 2 /* RADIAL_GRADIENT */; + return; + } + } + if (firstToken.type === 18 /* FUNCTION */) { + if (firstToken.name === 'from') { + var color = color$1.parse(context, firstToken.values[0]); + stops.push({ stop: ZERO_LENGTH, color: color }); + } + else if (firstToken.name === 'to') { + var color = color$1.parse(context, firstToken.values[0]); + stops.push({ stop: HUNDRED_PERCENT, color: color }); + } + else if (firstToken.name === 'color-stop') { + var values = firstToken.values.filter(nonFunctionArgSeparator); + if (values.length === 2) { + var color = color$1.parse(context, values[1]); + var stop_1 = values[0]; + if (isNumberToken(stop_1)) { + stops.push({ + stop: { type: 16 /* PERCENTAGE_TOKEN */, number: stop_1.number * 100, flags: stop_1.flags }, + color: color + }); + } + } + } + } + }); + return type === 1 /* LINEAR_GRADIENT */ + ? { + angle: (angle + deg(180)) % deg(360), + stops: stops, + type: type + } + : { size: size, shape: shape, stops: stops, position: position, type: type }; + }; + + var CLOSEST_SIDE = 'closest-side'; + var FARTHEST_SIDE = 'farthest-side'; + var CLOSEST_CORNER = 'closest-corner'; + var FARTHEST_CORNER = 'farthest-corner'; + var CIRCLE = 'circle'; + var ELLIPSE = 'ellipse'; + var COVER = 'cover'; + var CONTAIN = 'contain'; + var radialGradient = function (context, tokens) { + var shape = 0 /* CIRCLE */; + var size = 3 /* FARTHEST_CORNER */; + var stops = []; + var position = []; + parseFunctionArgs(tokens).forEach(function (arg, i) { + var isColorStop = true; + if (i === 0) { + var isAtPosition_1 = false; + isColorStop = arg.reduce(function (acc, token) { + if (isAtPosition_1) { + if (isIdentToken(token)) { + switch (token.value) { + case 'center': + position.push(FIFTY_PERCENT); + return acc; + case 'top': + case 'left': + position.push(ZERO_LENGTH); + return acc; + case 'right': + case 'bottom': + position.push(HUNDRED_PERCENT); + return acc; + } + } + else if (isLengthPercentage(token) || isLength(token)) { + position.push(token); + } + } + else if (isIdentToken(token)) { + switch (token.value) { + case CIRCLE: + shape = 0 /* CIRCLE */; + return false; + case ELLIPSE: + shape = 1 /* ELLIPSE */; + return false; + case 'at': + isAtPosition_1 = true; + return false; + case CLOSEST_SIDE: + size = 0 /* CLOSEST_SIDE */; + return false; + case COVER: + case FARTHEST_SIDE: + size = 1 /* FARTHEST_SIDE */; + return false; + case CONTAIN: + case CLOSEST_CORNER: + size = 2 /* CLOSEST_CORNER */; + return false; + case FARTHEST_CORNER: + size = 3 /* FARTHEST_CORNER */; + return false; + } + } + else if (isLength(token) || isLengthPercentage(token)) { + if (!Array.isArray(size)) { + size = []; + } + size.push(token); + return false; + } + return acc; + }, isColorStop); + } + if (isColorStop) { + var colorStop = parseColorStop(context, arg); + stops.push(colorStop); + } + }); + return { size: size, shape: shape, stops: stops, position: position, type: 2 /* RADIAL_GRADIENT */ }; + }; + + var prefixRadialGradient = function (context, tokens) { + var shape = 0 /* CIRCLE */; + var size = 3 /* FARTHEST_CORNER */; + var stops = []; + var position = []; + parseFunctionArgs(tokens).forEach(function (arg, i) { + var isColorStop = true; + if (i === 0) { + isColorStop = arg.reduce(function (acc, token) { + if (isIdentToken(token)) { + switch (token.value) { + case 'center': + position.push(FIFTY_PERCENT); + return false; + case 'top': + case 'left': + position.push(ZERO_LENGTH); + return false; + case 'right': + case 'bottom': + position.push(HUNDRED_PERCENT); + return false; + } + } + else if (isLengthPercentage(token) || isLength(token)) { + position.push(token); + return false; + } + return acc; + }, isColorStop); + } + else if (i === 1) { + isColorStop = arg.reduce(function (acc, token) { + if (isIdentToken(token)) { + switch (token.value) { + case CIRCLE: + shape = 0 /* CIRCLE */; + return false; + case ELLIPSE: + shape = 1 /* ELLIPSE */; + return false; + case CONTAIN: + case CLOSEST_SIDE: + size = 0 /* CLOSEST_SIDE */; + return false; + case FARTHEST_SIDE: + size = 1 /* FARTHEST_SIDE */; + return false; + case CLOSEST_CORNER: + size = 2 /* CLOSEST_CORNER */; + return false; + case COVER: + case FARTHEST_CORNER: + size = 3 /* FARTHEST_CORNER */; + return false; + } + } + else if (isLength(token) || isLengthPercentage(token)) { + if (!Array.isArray(size)) { + size = []; + } + size.push(token); + return false; + } + return acc; + }, isColorStop); + } + if (isColorStop) { + var colorStop = parseColorStop(context, arg); + stops.push(colorStop); + } + }); + return { size: size, shape: shape, stops: stops, position: position, type: 2 /* RADIAL_GRADIENT */ }; + }; + + var isLinearGradient = function (background) { + return background.type === 1 /* LINEAR_GRADIENT */; + }; + var isRadialGradient = function (background) { + return background.type === 2 /* RADIAL_GRADIENT */; + }; + var image = { + name: 'image', + parse: function (context, value) { + if (value.type === 22 /* URL_TOKEN */) { + var image_1 = { url: value.value, type: 0 /* URL */ }; + context.cache.addImage(value.value); + return image_1; + } + if (value.type === 18 /* FUNCTION */) { + var imageFunction = SUPPORTED_IMAGE_FUNCTIONS[value.name]; + if (typeof imageFunction === 'undefined') { + throw new Error("Attempting to parse an unsupported image function \"" + value.name + "\""); + } + return imageFunction(context, value.values); + } + throw new Error("Unsupported image type " + value.type); + } + }; + function isSupportedImage(value) { + return (!(value.type === 20 /* IDENT_TOKEN */ && value.value === 'none') && + (value.type !== 18 /* FUNCTION */ || !!SUPPORTED_IMAGE_FUNCTIONS[value.name])); + } + var SUPPORTED_IMAGE_FUNCTIONS = { + 'linear-gradient': linearGradient, + '-moz-linear-gradient': prefixLinearGradient, + '-ms-linear-gradient': prefixLinearGradient, + '-o-linear-gradient': prefixLinearGradient, + '-webkit-linear-gradient': prefixLinearGradient, + 'radial-gradient': radialGradient, + '-moz-radial-gradient': prefixRadialGradient, + '-ms-radial-gradient': prefixRadialGradient, + '-o-radial-gradient': prefixRadialGradient, + '-webkit-radial-gradient': prefixRadialGradient, + '-webkit-gradient': webkitGradient + }; + + var backgroundImage = { + name: 'background-image', + initialValue: 'none', + type: 1 /* LIST */, + prefix: false, + parse: function (context, tokens) { + if (tokens.length === 0) { + return []; + } + var first = tokens[0]; + if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') { + return []; + } + return tokens + .filter(function (value) { return nonFunctionArgSeparator(value) && isSupportedImage(value); }) + .map(function (value) { return image.parse(context, value); }); + } + }; + + var backgroundOrigin = { + name: 'background-origin', + initialValue: 'border-box', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return tokens.map(function (token) { + if (isIdentToken(token)) { + switch (token.value) { + case 'padding-box': + return 1 /* PADDING_BOX */; + case 'content-box': + return 2 /* CONTENT_BOX */; + } + } + return 0 /* BORDER_BOX */; + }); + } + }; + + var backgroundPosition = { + name: 'background-position', + initialValue: '0% 0%', + type: 1 /* LIST */, + prefix: false, + parse: function (_context, tokens) { + return parseFunctionArgs(tokens) + .map(function (values) { return values.filter(isLengthPercentage); }) + .map(parseLengthPercentageTuple); + } + }; + + var backgroundRepeat = { + name: 'background-repeat', + initialValue: 'repeat', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return parseFunctionArgs(tokens) + .map(function (values) { + return values + .filter(isIdentToken) + .map(function (token) { return token.value; }) + .join(' '); + }) + .map(parseBackgroundRepeat); + } + }; + var parseBackgroundRepeat = function (value) { + switch (value) { + case 'no-repeat': + return 1 /* NO_REPEAT */; + case 'repeat-x': + case 'repeat no-repeat': + return 2 /* REPEAT_X */; + case 'repeat-y': + case 'no-repeat repeat': + return 3 /* REPEAT_Y */; + case 'repeat': + default: + return 0 /* REPEAT */; + } + }; + + var BACKGROUND_SIZE; + (function (BACKGROUND_SIZE) { + BACKGROUND_SIZE["AUTO"] = "auto"; + BACKGROUND_SIZE["CONTAIN"] = "contain"; + BACKGROUND_SIZE["COVER"] = "cover"; + })(BACKGROUND_SIZE || (BACKGROUND_SIZE = {})); + var backgroundSize = { + name: 'background-size', + initialValue: '0', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return parseFunctionArgs(tokens).map(function (values) { return values.filter(isBackgroundSizeInfoToken); }); + } + }; + var isBackgroundSizeInfoToken = function (value) { + return isIdentToken(value) || isLengthPercentage(value); + }; + + var borderColorForSide = function (side) { return ({ + name: "border-" + side + "-color", + initialValue: 'transparent', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'color' + }); }; + var borderTopColor = borderColorForSide('top'); + var borderRightColor = borderColorForSide('right'); + var borderBottomColor = borderColorForSide('bottom'); + var borderLeftColor = borderColorForSide('left'); + + var borderRadiusForSide = function (side) { return ({ + name: "border-radius-" + side, + initialValue: '0 0', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return parseLengthPercentageTuple(tokens.filter(isLengthPercentage)); + } + }); }; + var borderTopLeftRadius = borderRadiusForSide('top-left'); + var borderTopRightRadius = borderRadiusForSide('top-right'); + var borderBottomRightRadius = borderRadiusForSide('bottom-right'); + var borderBottomLeftRadius = borderRadiusForSide('bottom-left'); + + var borderStyleForSide = function (side) { return ({ + name: "border-" + side + "-style", + initialValue: 'solid', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, style) { + switch (style) { + case 'none': + return 0 /* NONE */; + case 'dashed': + return 2 /* DASHED */; + case 'dotted': + return 3 /* DOTTED */; + case 'double': + return 4 /* DOUBLE */; + } + return 1 /* SOLID */; + } + }); }; + var borderTopStyle = borderStyleForSide('top'); + var borderRightStyle = borderStyleForSide('right'); + var borderBottomStyle = borderStyleForSide('bottom'); + var borderLeftStyle = borderStyleForSide('left'); + + var borderWidthForSide = function (side) { return ({ + name: "border-" + side + "-width", + initialValue: '0', + type: 0 /* VALUE */, + prefix: false, + parse: function (_context, token) { + if (isDimensionToken(token)) { + return token.number; + } + return 0; + } + }); }; + var borderTopWidth = borderWidthForSide('top'); + var borderRightWidth = borderWidthForSide('right'); + var borderBottomWidth = borderWidthForSide('bottom'); + var borderLeftWidth = borderWidthForSide('left'); + + var color = { + name: "color", + initialValue: 'transparent', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'color' + }; + + var direction = { + name: 'direction', + initialValue: 'ltr', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, direction) { + switch (direction) { + case 'rtl': + return 1 /* RTL */; + case 'ltr': + default: + return 0 /* LTR */; + } + } + }; + + var display = { + name: 'display', + initialValue: 'inline-block', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return tokens.filter(isIdentToken).reduce(function (bit, token) { + return bit | parseDisplayValue(token.value); + }, 0 /* NONE */); + } + }; + var parseDisplayValue = function (display) { + switch (display) { + case 'block': + case '-webkit-box': + return 2 /* BLOCK */; + case 'inline': + return 4 /* INLINE */; + case 'run-in': + return 8 /* RUN_IN */; + case 'flow': + return 16 /* FLOW */; + case 'flow-root': + return 32 /* FLOW_ROOT */; + case 'table': + return 64 /* TABLE */; + case 'flex': + case '-webkit-flex': + return 128 /* FLEX */; + case 'grid': + case '-ms-grid': + return 256 /* GRID */; + case 'ruby': + return 512 /* RUBY */; + case 'subgrid': + return 1024 /* SUBGRID */; + case 'list-item': + return 2048 /* LIST_ITEM */; + case 'table-row-group': + return 4096 /* TABLE_ROW_GROUP */; + case 'table-header-group': + return 8192 /* TABLE_HEADER_GROUP */; + case 'table-footer-group': + return 16384 /* TABLE_FOOTER_GROUP */; + case 'table-row': + return 32768 /* TABLE_ROW */; + case 'table-cell': + return 65536 /* TABLE_CELL */; + case 'table-column-group': + return 131072 /* TABLE_COLUMN_GROUP */; + case 'table-column': + return 262144 /* TABLE_COLUMN */; + case 'table-caption': + return 524288 /* TABLE_CAPTION */; + case 'ruby-base': + return 1048576 /* RUBY_BASE */; + case 'ruby-text': + return 2097152 /* RUBY_TEXT */; + case 'ruby-base-container': + return 4194304 /* RUBY_BASE_CONTAINER */; + case 'ruby-text-container': + return 8388608 /* RUBY_TEXT_CONTAINER */; + case 'contents': + return 16777216 /* CONTENTS */; + case 'inline-block': + return 33554432 /* INLINE_BLOCK */; + case 'inline-list-item': + return 67108864 /* INLINE_LIST_ITEM */; + case 'inline-table': + return 134217728 /* INLINE_TABLE */; + case 'inline-flex': + return 268435456 /* INLINE_FLEX */; + case 'inline-grid': + return 536870912 /* INLINE_GRID */; + } + return 0 /* NONE */; + }; + + var float = { + name: 'float', + initialValue: 'none', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, float) { + switch (float) { + case 'left': + return 1 /* LEFT */; + case 'right': + return 2 /* RIGHT */; + case 'inline-start': + return 3 /* INLINE_START */; + case 'inline-end': + return 4 /* INLINE_END */; + } + return 0 /* NONE */; + } + }; + + var letterSpacing = { + name: 'letter-spacing', + initialValue: '0', + prefix: false, + type: 0 /* VALUE */, + parse: function (_context, token) { + if (token.type === 20 /* IDENT_TOKEN */ && token.value === 'normal') { + return 0; + } + if (token.type === 17 /* NUMBER_TOKEN */) { + return token.number; + } + if (token.type === 15 /* DIMENSION_TOKEN */) { + return token.number; + } + return 0; + } + }; + + var LINE_BREAK; + (function (LINE_BREAK) { + LINE_BREAK["NORMAL"] = "normal"; + LINE_BREAK["STRICT"] = "strict"; + })(LINE_BREAK || (LINE_BREAK = {})); + var lineBreak = { + name: 'line-break', + initialValue: 'normal', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, lineBreak) { + switch (lineBreak) { + case 'strict': + return LINE_BREAK.STRICT; + case 'normal': + default: + return LINE_BREAK.NORMAL; + } + } + }; + + var lineHeight = { + name: 'line-height', + initialValue: 'normal', + prefix: false, + type: 4 /* TOKEN_VALUE */ + }; + var computeLineHeight = function (token, fontSize) { + if (isIdentToken(token) && token.value === 'normal') { + return 1.2 * fontSize; + } + else if (token.type === 17 /* NUMBER_TOKEN */) { + return fontSize * token.number; + } + else if (isLengthPercentage(token)) { + return getAbsoluteValue(token, fontSize); + } + return fontSize; + }; + + var listStyleImage = { + name: 'list-style-image', + initialValue: 'none', + type: 0 /* VALUE */, + prefix: false, + parse: function (context, token) { + if (token.type === 20 /* IDENT_TOKEN */ && token.value === 'none') { + return null; + } + return image.parse(context, token); + } + }; + + var listStylePosition = { + name: 'list-style-position', + initialValue: 'outside', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, position) { + switch (position) { + case 'inside': + return 0 /* INSIDE */; + case 'outside': + default: + return 1 /* OUTSIDE */; + } + } + }; + + var listStyleType = { + name: 'list-style-type', + initialValue: 'none', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, type) { + switch (type) { + case 'disc': + return 0 /* DISC */; + case 'circle': + return 1 /* CIRCLE */; + case 'square': + return 2 /* SQUARE */; + case 'decimal': + return 3 /* DECIMAL */; + case 'cjk-decimal': + return 4 /* CJK_DECIMAL */; + case 'decimal-leading-zero': + return 5 /* DECIMAL_LEADING_ZERO */; + case 'lower-roman': + return 6 /* LOWER_ROMAN */; + case 'upper-roman': + return 7 /* UPPER_ROMAN */; + case 'lower-greek': + return 8 /* LOWER_GREEK */; + case 'lower-alpha': + return 9 /* LOWER_ALPHA */; + case 'upper-alpha': + return 10 /* UPPER_ALPHA */; + case 'arabic-indic': + return 11 /* ARABIC_INDIC */; + case 'armenian': + return 12 /* ARMENIAN */; + case 'bengali': + return 13 /* BENGALI */; + case 'cambodian': + return 14 /* CAMBODIAN */; + case 'cjk-earthly-branch': + return 15 /* CJK_EARTHLY_BRANCH */; + case 'cjk-heavenly-stem': + return 16 /* CJK_HEAVENLY_STEM */; + case 'cjk-ideographic': + return 17 /* CJK_IDEOGRAPHIC */; + case 'devanagari': + return 18 /* DEVANAGARI */; + case 'ethiopic-numeric': + return 19 /* ETHIOPIC_NUMERIC */; + case 'georgian': + return 20 /* GEORGIAN */; + case 'gujarati': + return 21 /* GUJARATI */; + case 'gurmukhi': + return 22 /* GURMUKHI */; + case 'hebrew': + return 22 /* HEBREW */; + case 'hiragana': + return 23 /* HIRAGANA */; + case 'hiragana-iroha': + return 24 /* HIRAGANA_IROHA */; + case 'japanese-formal': + return 25 /* JAPANESE_FORMAL */; + case 'japanese-informal': + return 26 /* JAPANESE_INFORMAL */; + case 'kannada': + return 27 /* KANNADA */; + case 'katakana': + return 28 /* KATAKANA */; + case 'katakana-iroha': + return 29 /* KATAKANA_IROHA */; + case 'khmer': + return 30 /* KHMER */; + case 'korean-hangul-formal': + return 31 /* KOREAN_HANGUL_FORMAL */; + case 'korean-hanja-formal': + return 32 /* KOREAN_HANJA_FORMAL */; + case 'korean-hanja-informal': + return 33 /* KOREAN_HANJA_INFORMAL */; + case 'lao': + return 34 /* LAO */; + case 'lower-armenian': + return 35 /* LOWER_ARMENIAN */; + case 'malayalam': + return 36 /* MALAYALAM */; + case 'mongolian': + return 37 /* MONGOLIAN */; + case 'myanmar': + return 38 /* MYANMAR */; + case 'oriya': + return 39 /* ORIYA */; + case 'persian': + return 40 /* PERSIAN */; + case 'simp-chinese-formal': + return 41 /* SIMP_CHINESE_FORMAL */; + case 'simp-chinese-informal': + return 42 /* SIMP_CHINESE_INFORMAL */; + case 'tamil': + return 43 /* TAMIL */; + case 'telugu': + return 44 /* TELUGU */; + case 'thai': + return 45 /* THAI */; + case 'tibetan': + return 46 /* TIBETAN */; + case 'trad-chinese-formal': + return 47 /* TRAD_CHINESE_FORMAL */; + case 'trad-chinese-informal': + return 48 /* TRAD_CHINESE_INFORMAL */; + case 'upper-armenian': + return 49 /* UPPER_ARMENIAN */; + case 'disclosure-open': + return 50 /* DISCLOSURE_OPEN */; + case 'disclosure-closed': + return 51 /* DISCLOSURE_CLOSED */; + case 'none': + default: + return -1 /* NONE */; + } + } + }; + + var marginForSide = function (side) { return ({ + name: "margin-" + side, + initialValue: '0', + prefix: false, + type: 4 /* TOKEN_VALUE */ + }); }; + var marginTop = marginForSide('top'); + var marginRight = marginForSide('right'); + var marginBottom = marginForSide('bottom'); + var marginLeft = marginForSide('left'); + + var overflow = { + name: 'overflow', + initialValue: 'visible', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return tokens.filter(isIdentToken).map(function (overflow) { + switch (overflow.value) { + case 'hidden': + return 1 /* HIDDEN */; + case 'scroll': + return 2 /* SCROLL */; + case 'clip': + return 3 /* CLIP */; + case 'auto': + return 4 /* AUTO */; + case 'visible': + default: + return 0 /* VISIBLE */; + } + }); + } + }; + + var overflowWrap = { + name: 'overflow-wrap', + initialValue: 'normal', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, overflow) { + switch (overflow) { + case 'break-word': + return "break-word" /* BREAK_WORD */; + case 'normal': + default: + return "normal" /* NORMAL */; + } + } + }; + + var paddingForSide = function (side) { return ({ + name: "padding-" + side, + initialValue: '0', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'length-percentage' + }); }; + var paddingTop = paddingForSide('top'); + var paddingRight = paddingForSide('right'); + var paddingBottom = paddingForSide('bottom'); + var paddingLeft = paddingForSide('left'); + + var textAlign = { + name: 'text-align', + initialValue: 'left', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, textAlign) { + switch (textAlign) { + case 'right': + return 2 /* RIGHT */; + case 'center': + case 'justify': + return 1 /* CENTER */; + case 'left': + default: + return 0 /* LEFT */; + } + } + }; + + var position = { + name: 'position', + initialValue: 'static', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, position) { + switch (position) { + case 'relative': + return 1 /* RELATIVE */; + case 'absolute': + return 2 /* ABSOLUTE */; + case 'fixed': + return 3 /* FIXED */; + case 'sticky': + return 4 /* STICKY */; + } + return 0 /* STATIC */; + } + }; + + var textShadow = { + name: 'text-shadow', + initialValue: 'none', + type: 1 /* LIST */, + prefix: false, + parse: function (context, tokens) { + if (tokens.length === 1 && isIdentWithValue(tokens[0], 'none')) { + return []; + } + return parseFunctionArgs(tokens).map(function (values) { + var shadow = { + color: COLORS.TRANSPARENT, + offsetX: ZERO_LENGTH, + offsetY: ZERO_LENGTH, + blur: ZERO_LENGTH + }; + var c = 0; + for (var i = 0; i < values.length; i++) { + var token = values[i]; + if (isLength(token)) { + if (c === 0) { + shadow.offsetX = token; + } + else if (c === 1) { + shadow.offsetY = token; + } + else { + shadow.blur = token; + } + c++; + } + else { + shadow.color = color$1.parse(context, token); + } + } + return shadow; + }); + } + }; + + var textTransform = { + name: 'text-transform', + initialValue: 'none', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, textTransform) { + switch (textTransform) { + case 'uppercase': + return 2 /* UPPERCASE */; + case 'lowercase': + return 1 /* LOWERCASE */; + case 'capitalize': + return 3 /* CAPITALIZE */; + } + return 0 /* NONE */; + } + }; + + var transform$1 = { + name: 'transform', + initialValue: 'none', + prefix: true, + type: 0 /* VALUE */, + parse: function (_context, token) { + if (token.type === 20 /* IDENT_TOKEN */ && token.value === 'none') { + return null; + } + if (token.type === 18 /* FUNCTION */) { + var transformFunction = SUPPORTED_TRANSFORM_FUNCTIONS[token.name]; + if (typeof transformFunction === 'undefined') { + throw new Error("Attempting to parse an unsupported transform function \"" + token.name + "\""); + } + return transformFunction(token.values); + } + return null; + } + }; + var matrix = function (args) { + var values = args.filter(function (arg) { return arg.type === 17 /* NUMBER_TOKEN */; }).map(function (arg) { return arg.number; }); + return values.length === 6 ? values : null; + }; + // doesn't support 3D transforms at the moment + var matrix3d = function (args) { + var values = args.filter(function (arg) { return arg.type === 17 /* NUMBER_TOKEN */; }).map(function (arg) { return arg.number; }); + var a1 = values[0], b1 = values[1]; values[2]; values[3]; var a2 = values[4], b2 = values[5]; values[6]; values[7]; values[8]; values[9]; values[10]; values[11]; var a4 = values[12], b4 = values[13]; values[14]; values[15]; + return values.length === 16 ? [a1, b1, a2, b2, a4, b4] : null; + }; + var SUPPORTED_TRANSFORM_FUNCTIONS = { + matrix: matrix, + matrix3d: matrix3d + }; + + var DEFAULT_VALUE = { + type: 16 /* PERCENTAGE_TOKEN */, + number: 50, + flags: FLAG_INTEGER + }; + var DEFAULT = [DEFAULT_VALUE, DEFAULT_VALUE]; + var transformOrigin = { + name: 'transform-origin', + initialValue: '50% 50%', + prefix: true, + type: 1 /* LIST */, + parse: function (_context, tokens) { + var origins = tokens.filter(isLengthPercentage); + if (origins.length !== 2) { + return DEFAULT; + } + return [origins[0], origins[1]]; + } + }; + + var visibility = { + name: 'visible', + initialValue: 'none', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, visibility) { + switch (visibility) { + case 'hidden': + return 1 /* HIDDEN */; + case 'collapse': + return 2 /* COLLAPSE */; + case 'visible': + default: + return 0 /* VISIBLE */; + } + } + }; + + var WORD_BREAK; + (function (WORD_BREAK) { + WORD_BREAK["NORMAL"] = "normal"; + WORD_BREAK["BREAK_ALL"] = "break-all"; + WORD_BREAK["KEEP_ALL"] = "keep-all"; + })(WORD_BREAK || (WORD_BREAK = {})); + var wordBreak = { + name: 'word-break', + initialValue: 'normal', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, wordBreak) { + switch (wordBreak) { + case 'break-all': + return WORD_BREAK.BREAK_ALL; + case 'keep-all': + return WORD_BREAK.KEEP_ALL; + case 'normal': + default: + return WORD_BREAK.NORMAL; + } + } + }; + + var zIndex = { + name: 'z-index', + initialValue: 'auto', + prefix: false, + type: 0 /* VALUE */, + parse: function (_context, token) { + if (token.type === 20 /* IDENT_TOKEN */) { + return { auto: true, order: 0 }; + } + if (isNumberToken(token)) { + return { auto: false, order: token.number }; + } + throw new Error("Invalid z-index number parsed"); + } + }; + + var time = { + name: 'time', + parse: function (_context, value) { + if (value.type === 15 /* DIMENSION_TOKEN */) { + switch (value.unit.toLowerCase()) { + case 's': + return 1000 * value.number; + case 'ms': + return value.number; + } + } + throw new Error("Unsupported time type"); + } + }; + + var opacity = { + name: 'opacity', + initialValue: '1', + type: 0 /* VALUE */, + prefix: false, + parse: function (_context, token) { + if (isNumberToken(token)) { + return token.number; + } + return 1; + } + }; + + var textDecorationColor = { + name: "text-decoration-color", + initialValue: 'transparent', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'color' + }; + + var textDecorationLine = { + name: 'text-decoration-line', + initialValue: 'none', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + return tokens + .filter(isIdentToken) + .map(function (token) { + switch (token.value) { + case 'underline': + return 1 /* UNDERLINE */; + case 'overline': + return 2 /* OVERLINE */; + case 'line-through': + return 3 /* LINE_THROUGH */; + case 'none': + return 4 /* BLINK */; + } + return 0 /* NONE */; + }) + .filter(function (line) { return line !== 0 /* NONE */; }); + } + }; + + var fontFamily = { + name: "font-family", + initialValue: '', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + var accumulator = []; + var results = []; + tokens.forEach(function (token) { + switch (token.type) { + case 20 /* IDENT_TOKEN */: + case 0 /* STRING_TOKEN */: + accumulator.push(token.value); + break; + case 17 /* NUMBER_TOKEN */: + accumulator.push(token.number.toString()); + break; + case 4 /* COMMA_TOKEN */: + results.push(accumulator.join(' ')); + accumulator.length = 0; + break; + } + }); + if (accumulator.length) { + results.push(accumulator.join(' ')); + } + return results.map(function (result) { return (result.indexOf(' ') === -1 ? result : "'" + result + "'"); }); + } + }; + + var fontSize = { + name: "font-size", + initialValue: '0', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'length' + }; + + var fontWeight = { + name: 'font-weight', + initialValue: 'normal', + type: 0 /* VALUE */, + prefix: false, + parse: function (_context, token) { + if (isNumberToken(token)) { + return token.number; + } + if (isIdentToken(token)) { + switch (token.value) { + case 'bold': + return 700; + case 'normal': + default: + return 400; + } + } + return 400; + } + }; + + var fontVariant = { + name: 'font-variant', + initialValue: 'none', + type: 1 /* LIST */, + prefix: false, + parse: function (_context, tokens) { + return tokens.filter(isIdentToken).map(function (token) { return token.value; }); + } + }; + + var fontStyle = { + name: 'font-style', + initialValue: 'normal', + prefix: false, + type: 2 /* IDENT_VALUE */, + parse: function (_context, overflow) { + switch (overflow) { + case 'oblique': + return "oblique" /* OBLIQUE */; + case 'italic': + return "italic" /* ITALIC */; + case 'normal': + default: + return "normal" /* NORMAL */; + } + } + }; + + var contains = function (bit, value) { return (bit & value) !== 0; }; + + var content = { + name: 'content', + initialValue: 'none', + type: 1 /* LIST */, + prefix: false, + parse: function (_context, tokens) { + if (tokens.length === 0) { + return []; + } + var first = tokens[0]; + if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') { + return []; + } + return tokens; + } + }; + + var counterIncrement = { + name: 'counter-increment', + initialValue: 'none', + prefix: true, + type: 1 /* LIST */, + parse: function (_context, tokens) { + if (tokens.length === 0) { + return null; + } + var first = tokens[0]; + if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') { + return null; + } + var increments = []; + var filtered = tokens.filter(nonWhiteSpace); + for (var i = 0; i < filtered.length; i++) { + var counter = filtered[i]; + var next = filtered[i + 1]; + if (counter.type === 20 /* IDENT_TOKEN */) { + var increment = next && isNumberToken(next) ? next.number : 1; + increments.push({ counter: counter.value, increment: increment }); + } + } + return increments; + } + }; + + var counterReset = { + name: 'counter-reset', + initialValue: 'none', + prefix: true, + type: 1 /* LIST */, + parse: function (_context, tokens) { + if (tokens.length === 0) { + return []; + } + var resets = []; + var filtered = tokens.filter(nonWhiteSpace); + for (var i = 0; i < filtered.length; i++) { + var counter = filtered[i]; + var next = filtered[i + 1]; + if (isIdentToken(counter) && counter.value !== 'none') { + var reset = next && isNumberToken(next) ? next.number : 0; + resets.push({ counter: counter.value, reset: reset }); + } + } + return resets; + } + }; + + var duration = { + name: 'duration', + initialValue: '0s', + prefix: false, + type: 1 /* LIST */, + parse: function (context, tokens) { + return tokens.filter(isDimensionToken).map(function (token) { return time.parse(context, token); }); + } + }; + + var quotes = { + name: 'quotes', + initialValue: 'none', + prefix: true, + type: 1 /* LIST */, + parse: function (_context, tokens) { + if (tokens.length === 0) { + return null; + } + var first = tokens[0]; + if (first.type === 20 /* IDENT_TOKEN */ && first.value === 'none') { + return null; + } + var quotes = []; + var filtered = tokens.filter(isStringToken); + if (filtered.length % 2 !== 0) { + return null; + } + for (var i = 0; i < filtered.length; i += 2) { + var open_1 = filtered[i].value; + var close_1 = filtered[i + 1].value; + quotes.push({ open: open_1, close: close_1 }); + } + return quotes; + } + }; + var getQuote = function (quotes, depth, open) { + if (!quotes) { + return ''; + } + var quote = quotes[Math.min(depth, quotes.length - 1)]; + if (!quote) { + return ''; + } + return open ? quote.open : quote.close; + }; + + var boxShadow = { + name: 'box-shadow', + initialValue: 'none', + type: 1 /* LIST */, + prefix: false, + parse: function (context, tokens) { + if (tokens.length === 1 && isIdentWithValue(tokens[0], 'none')) { + return []; + } + return parseFunctionArgs(tokens).map(function (values) { + var shadow = { + color: 0x000000ff, + offsetX: ZERO_LENGTH, + offsetY: ZERO_LENGTH, + blur: ZERO_LENGTH, + spread: ZERO_LENGTH, + inset: false + }; + var c = 0; + for (var i = 0; i < values.length; i++) { + var token = values[i]; + if (isIdentWithValue(token, 'inset')) { + shadow.inset = true; + } + else if (isLength(token)) { + if (c === 0) { + shadow.offsetX = token; + } + else if (c === 1) { + shadow.offsetY = token; + } + else if (c === 2) { + shadow.blur = token; + } + else { + shadow.spread = token; + } + c++; + } + else { + shadow.color = color$1.parse(context, token); + } + } + return shadow; + }); + } + }; + + var paintOrder = { + name: 'paint-order', + initialValue: 'normal', + prefix: false, + type: 1 /* LIST */, + parse: function (_context, tokens) { + var DEFAULT_VALUE = [0 /* FILL */, 1 /* STROKE */, 2 /* MARKERS */]; + var layers = []; + tokens.filter(isIdentToken).forEach(function (token) { + switch (token.value) { + case 'stroke': + layers.push(1 /* STROKE */); + break; + case 'fill': + layers.push(0 /* FILL */); + break; + case 'markers': + layers.push(2 /* MARKERS */); + break; + } + }); + DEFAULT_VALUE.forEach(function (value) { + if (layers.indexOf(value) === -1) { + layers.push(value); + } + }); + return layers; + } + }; + + var webkitTextStrokeColor = { + name: "-webkit-text-stroke-color", + initialValue: 'currentcolor', + prefix: false, + type: 3 /* TYPE_VALUE */, + format: 'color' + }; + + var webkitTextStrokeWidth = { + name: "-webkit-text-stroke-width", + initialValue: '0', + type: 0 /* VALUE */, + prefix: false, + parse: function (_context, token) { + if (isDimensionToken(token)) { + return token.number; + } + return 0; + } + }; + + var CSSParsedDeclaration = /** @class */ (function () { + function CSSParsedDeclaration(context, declaration) { + var _a, _b; + this.animationDuration = parse(context, duration, declaration.animationDuration); + this.backgroundClip = parse(context, backgroundClip, declaration.backgroundClip); + this.backgroundColor = parse(context, backgroundColor, declaration.backgroundColor); + this.backgroundImage = parse(context, backgroundImage, declaration.backgroundImage); + this.backgroundOrigin = parse(context, backgroundOrigin, declaration.backgroundOrigin); + this.backgroundPosition = parse(context, backgroundPosition, declaration.backgroundPosition); + this.backgroundRepeat = parse(context, backgroundRepeat, declaration.backgroundRepeat); + this.backgroundSize = parse(context, backgroundSize, declaration.backgroundSize); + this.borderTopColor = parse(context, borderTopColor, declaration.borderTopColor); + this.borderRightColor = parse(context, borderRightColor, declaration.borderRightColor); + this.borderBottomColor = parse(context, borderBottomColor, declaration.borderBottomColor); + this.borderLeftColor = parse(context, borderLeftColor, declaration.borderLeftColor); + this.borderTopLeftRadius = parse(context, borderTopLeftRadius, declaration.borderTopLeftRadius); + this.borderTopRightRadius = parse(context, borderTopRightRadius, declaration.borderTopRightRadius); + this.borderBottomRightRadius = parse(context, borderBottomRightRadius, declaration.borderBottomRightRadius); + this.borderBottomLeftRadius = parse(context, borderBottomLeftRadius, declaration.borderBottomLeftRadius); + this.borderTopStyle = parse(context, borderTopStyle, declaration.borderTopStyle); + this.borderRightStyle = parse(context, borderRightStyle, declaration.borderRightStyle); + this.borderBottomStyle = parse(context, borderBottomStyle, declaration.borderBottomStyle); + this.borderLeftStyle = parse(context, borderLeftStyle, declaration.borderLeftStyle); + this.borderTopWidth = parse(context, borderTopWidth, declaration.borderTopWidth); + this.borderRightWidth = parse(context, borderRightWidth, declaration.borderRightWidth); + this.borderBottomWidth = parse(context, borderBottomWidth, declaration.borderBottomWidth); + this.borderLeftWidth = parse(context, borderLeftWidth, declaration.borderLeftWidth); + this.boxShadow = parse(context, boxShadow, declaration.boxShadow); + this.color = parse(context, color, declaration.color); + this.direction = parse(context, direction, declaration.direction); + this.display = parse(context, display, declaration.display); + this.float = parse(context, float, declaration.cssFloat); + this.fontFamily = parse(context, fontFamily, declaration.fontFamily); + this.fontSize = parse(context, fontSize, declaration.fontSize); + this.fontStyle = parse(context, fontStyle, declaration.fontStyle); + this.fontVariant = parse(context, fontVariant, declaration.fontVariant); + this.fontWeight = parse(context, fontWeight, declaration.fontWeight); + this.letterSpacing = parse(context, letterSpacing, declaration.letterSpacing); + this.lineBreak = parse(context, lineBreak, declaration.lineBreak); + this.lineHeight = parse(context, lineHeight, declaration.lineHeight); + this.listStyleImage = parse(context, listStyleImage, declaration.listStyleImage); + this.listStylePosition = parse(context, listStylePosition, declaration.listStylePosition); + this.listStyleType = parse(context, listStyleType, declaration.listStyleType); + this.marginTop = parse(context, marginTop, declaration.marginTop); + this.marginRight = parse(context, marginRight, declaration.marginRight); + this.marginBottom = parse(context, marginBottom, declaration.marginBottom); + this.marginLeft = parse(context, marginLeft, declaration.marginLeft); + this.opacity = parse(context, opacity, declaration.opacity); + var overflowTuple = parse(context, overflow, declaration.overflow); + this.overflowX = overflowTuple[0]; + this.overflowY = overflowTuple[overflowTuple.length > 1 ? 1 : 0]; + this.overflowWrap = parse(context, overflowWrap, declaration.overflowWrap); + this.paddingTop = parse(context, paddingTop, declaration.paddingTop); + this.paddingRight = parse(context, paddingRight, declaration.paddingRight); + this.paddingBottom = parse(context, paddingBottom, declaration.paddingBottom); + this.paddingLeft = parse(context, paddingLeft, declaration.paddingLeft); + this.paintOrder = parse(context, paintOrder, declaration.paintOrder); + this.position = parse(context, position, declaration.position); + this.textAlign = parse(context, textAlign, declaration.textAlign); + this.textDecorationColor = parse(context, textDecorationColor, (_a = declaration.textDecorationColor) !== null && _a !== void 0 ? _a : declaration.color); + this.textDecorationLine = parse(context, textDecorationLine, (_b = declaration.textDecorationLine) !== null && _b !== void 0 ? _b : declaration.textDecoration); + this.textShadow = parse(context, textShadow, declaration.textShadow); + this.textTransform = parse(context, textTransform, declaration.textTransform); + this.transform = parse(context, transform$1, declaration.transform); + this.transformOrigin = parse(context, transformOrigin, declaration.transformOrigin); + this.visibility = parse(context, visibility, declaration.visibility); + this.webkitTextStrokeColor = parse(context, webkitTextStrokeColor, declaration.webkitTextStrokeColor); + this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth); + this.wordBreak = parse(context, wordBreak, declaration.wordBreak); + this.zIndex = parse(context, zIndex, declaration.zIndex); + } + CSSParsedDeclaration.prototype.isVisible = function () { + return this.display > 0 && this.opacity > 0 && this.visibility === 0 /* VISIBLE */; + }; + CSSParsedDeclaration.prototype.isTransparent = function () { + return isTransparent(this.backgroundColor); + }; + CSSParsedDeclaration.prototype.isTransformed = function () { + return this.transform !== null; + }; + CSSParsedDeclaration.prototype.isPositioned = function () { + return this.position !== 0 /* STATIC */; + }; + CSSParsedDeclaration.prototype.isPositionedWithZIndex = function () { + return this.isPositioned() && !this.zIndex.auto; + }; + CSSParsedDeclaration.prototype.isFloating = function () { + return this.float !== 0 /* NONE */; + }; + CSSParsedDeclaration.prototype.isInlineLevel = function () { + return (contains(this.display, 4 /* INLINE */) || + contains(this.display, 33554432 /* INLINE_BLOCK */) || + contains(this.display, 268435456 /* INLINE_FLEX */) || + contains(this.display, 536870912 /* INLINE_GRID */) || + contains(this.display, 67108864 /* INLINE_LIST_ITEM */) || + contains(this.display, 134217728 /* INLINE_TABLE */)); + }; + return CSSParsedDeclaration; + }()); + var CSSParsedPseudoDeclaration = /** @class */ (function () { + function CSSParsedPseudoDeclaration(context, declaration) { + this.content = parse(context, content, declaration.content); + this.quotes = parse(context, quotes, declaration.quotes); + } + return CSSParsedPseudoDeclaration; + }()); + var CSSParsedCounterDeclaration = /** @class */ (function () { + function CSSParsedCounterDeclaration(context, declaration) { + this.counterIncrement = parse(context, counterIncrement, declaration.counterIncrement); + this.counterReset = parse(context, counterReset, declaration.counterReset); + } + return CSSParsedCounterDeclaration; + }()); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var parse = function (context, descriptor, style) { + var tokenizer = new Tokenizer(); + var value = style !== null && typeof style !== 'undefined' ? style.toString() : descriptor.initialValue; + tokenizer.write(value); + var parser = new Parser(tokenizer.read()); + switch (descriptor.type) { + case 2 /* IDENT_VALUE */: + var token = parser.parseComponentValue(); + return descriptor.parse(context, isIdentToken(token) ? token.value : descriptor.initialValue); + case 0 /* VALUE */: + return descriptor.parse(context, parser.parseComponentValue()); + case 1 /* LIST */: + return descriptor.parse(context, parser.parseComponentValues()); + case 4 /* TOKEN_VALUE */: + return parser.parseComponentValue(); + case 3 /* TYPE_VALUE */: + switch (descriptor.format) { + case 'angle': + return angle.parse(context, parser.parseComponentValue()); + case 'color': + return color$1.parse(context, parser.parseComponentValue()); + case 'image': + return image.parse(context, parser.parseComponentValue()); + case 'length': + var length_1 = parser.parseComponentValue(); + return isLength(length_1) ? length_1 : ZERO_LENGTH; + case 'length-percentage': + var value_1 = parser.parseComponentValue(); + return isLengthPercentage(value_1) ? value_1 : ZERO_LENGTH; + case 'time': + return time.parse(context, parser.parseComponentValue()); + } + break; + } + }; + + var elementDebuggerAttribute = 'data-html2canvas-debug'; + var getElementDebugType = function (element) { + var attribute = element.getAttribute(elementDebuggerAttribute); + switch (attribute) { + case 'all': + return 1 /* ALL */; + case 'clone': + return 2 /* CLONE */; + case 'parse': + return 3 /* PARSE */; + case 'render': + return 4 /* RENDER */; + default: + return 0 /* NONE */; + } + }; + var isDebugging = function (element, type) { + var elementType = getElementDebugType(element); + return elementType === 1 /* ALL */ || type === elementType; + }; + + var ElementContainer = /** @class */ (function () { + function ElementContainer(context, element) { + this.context = context; + this.textNodes = []; + this.elements = []; + this.flags = 0; + if (isDebugging(element, 3 /* PARSE */)) { + debugger; + } + this.styles = new CSSParsedDeclaration(context, window.getComputedStyle(element, null)); + if (isHTMLElementNode(element)) { + if (this.styles.animationDuration.some(function (duration) { return duration > 0; })) { + element.style.animationDuration = '0s'; + } + if (this.styles.transform !== null) { + // getBoundingClientRect takes transforms into account + element.style.transform = 'none'; + } + } + this.bounds = parseBounds(this.context, element); + if (isDebugging(element, 4 /* RENDER */)) { + this.flags |= 16 /* DEBUG_RENDER */; + } + } + return ElementContainer; + }()); + + /* + * text-segmentation 1.0.3 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var base64 = 'AAAAAAAAAAAAEA4AGBkAAFAaAAACAAAAAAAIABAAGAAwADgACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAAQABIAEQATAAIABAACAAQAAgAEAAIABAAVABcAAgAEAAIABAACAAQAGAAaABwAHgAgACIAI4AlgAIABAAmwCjAKgAsAC2AL4AvQDFAMoA0gBPAVYBWgEIAAgACACMANoAYgFkAWwBdAF8AX0BhQGNAZUBlgGeAaMBlQGWAasBswF8AbsBwwF0AcsBYwHTAQgA2wG/AOMBdAF8AekB8QF0AfkB+wHiAHQBfAEIAAMC5gQIAAsCEgIIAAgAFgIeAggAIgIpAggAMQI5AkACygEIAAgASAJQAlgCYAIIAAgACAAKBQoFCgUTBRMFGQUrBSsFCAAIAAgACAAIAAgACAAIAAgACABdAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABoAmgCrwGvAQgAbgJ2AggAHgEIAAgACADnAXsCCAAIAAgAgwIIAAgACAAIAAgACACKAggAkQKZAggAPADJAAgAoQKkAqwCsgK6AsICCADJAggA0AIIAAgACAAIANYC3gIIAAgACAAIAAgACABAAOYCCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAkASoB+QIEAAgACAA8AEMCCABCBQgACABJBVAFCAAIAAgACAAIAAgACAAIAAgACABTBVoFCAAIAFoFCABfBWUFCAAIAAgACAAIAAgAbQUIAAgACAAIAAgACABzBXsFfQWFBYoFigWKBZEFigWKBYoFmAWfBaYFrgWxBbkFCAAIAAgACAAIAAgACAAIAAgACAAIAMEFCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAMgFCADQBQgACAAIAAgACAAIAAgACAAIAAgACAAIAO4CCAAIAAgAiQAIAAgACABAAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAD0AggACAD8AggACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIANYFCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAMDvwAIAAgAJAIIAAgACAAIAAgACAAIAAgACwMTAwgACAB9BOsEGwMjAwgAKwMyAwsFYgE3A/MEPwMIAEUDTQNRAwgAWQOsAGEDCAAIAAgACAAIAAgACABpAzQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFOgU0BTUFNgU3BTgFOQU6BTQFNQU2BTcFOAU5BToFNAU1BTYFNwU4BTkFIQUoBSwFCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABtAwgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABMAEwACAAIAAgACAAIABgACAAIAAgACAC/AAgACAAyAQgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACACAAIAAwAAgACAAIAAgACAAIAAgACAAIAAAARABIAAgACAAIABQASAAIAAgAIABwAEAAjgCIABsAqAC2AL0AigDQAtwC+IJIQqVAZUBWQqVAZUBlQGVAZUBlQGrC5UBlQGVAZUBlQGVAZUBlQGVAXsKlQGVAbAK6wsrDGUMpQzlDJUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAZUBlQGVAfAKAAuZA64AtwCJALoC6ADwAAgAuACgA/oEpgO6AqsD+AAIAAgAswMIAAgACAAIAIkAuwP5AfsBwwPLAwgACAAIAAgACADRA9kDCAAIAOED6QMIAAgACAAIAAgACADuA/YDCAAIAP4DyQAIAAgABgQIAAgAXQAOBAgACAAIAAgACAAIABMECAAIAAgACAAIAAgACAD8AAQBCAAIAAgAGgQiBCoECAExBAgAEAEIAAgACAAIAAgACAAIAAgACAAIAAgACAA4BAgACABABEYECAAIAAgATAQYAQgAVAQIAAgACAAIAAgACAAIAAgACAAIAFoECAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAOQEIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAB+BAcACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAEABhgSMBAgACAAIAAgAlAQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAwAEAAQABAADAAMAAwADAAQABAAEAAQABAAEAAQABHATAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAdQMIAAgACAAIAAgACAAIAMkACAAIAAgAfQMIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACACFA4kDCAAIAAgACAAIAOcBCAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAIcDCAAIAAgACAAIAAgACAAIAAgACAAIAJEDCAAIAAgACADFAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABgBAgAZgQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAbAQCBXIECAAIAHkECAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABAAJwEQACjBKoEsgQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAC6BMIECAAIAAgACAAIAAgACABmBAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAxwQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAGYECAAIAAgAzgQIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAigWKBYoFigWKBYoFigWKBd0FXwUIAOIF6gXxBYoF3gT5BQAGCAaKBYoFigWKBYoFigWKBYoFigWKBYoFigXWBIoFigWKBYoFigWKBYoFigWKBYsFEAaKBYoFigWKBYoFigWKBRQGCACKBYoFigWKBQgACAAIANEECAAIABgGigUgBggAJgYIAC4GMwaKBYoF0wQ3Bj4GigWKBYoFigWKBYoFigWKBYoFigWKBYoFigUIAAgACAAIAAgACAAIAAgAigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWKBYoFigWLBf///////wQABAAEAAQABAAEAAQABAAEAAQAAwAEAAQAAgAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAQADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAUAAAAFAAUAAAAFAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUAAQAAAAUABQAFAAUABQAFAAAAAAAFAAUAAAAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAFAAUAAQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABwAFAAUABQAFAAAABwAHAAcAAAAHAAcABwAFAAEAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAcABwAFAAUAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAQABAAAAAAAAAAAAAAAFAAUABQAFAAAABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAcABwAHAAcAAAAHAAcAAAAAAAUABQAHAAUAAQAHAAEABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABwABAAUABQAFAAUAAAAAAAAAAAAAAAEAAQABAAEAAQABAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABwAFAAUAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUAAQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABQANAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEAAQABAAEAAQABAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAABQAHAAUABQAFAAAAAAAAAAcABQAFAAUABQAFAAQABAAEAAQABAAEAAQABAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUAAAAFAAUABQAFAAUAAAAFAAUABQAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAAAAAAAAAAAAUABQAFAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAUAAAAHAAcABwAFAAUABQAFAAUABQAFAAUABwAHAAcABwAFAAcABwAAAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUABwAHAAUABQAFAAUAAAAAAAcABwAAAAAABwAHAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAABQAFAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABwAHAAcABQAFAAAAAAAAAAAABQAFAAAAAAAFAAUABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAFAAUABQAFAAUAAAAFAAUABwAAAAcABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAUABwAFAAUABQAFAAAAAAAHAAcAAAAAAAcABwAFAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAcABwAAAAAAAAAHAAcABwAAAAcABwAHAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAABQAHAAcABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAHAAcABwAAAAUABQAFAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAcABQAHAAcABQAHAAcAAAAFAAcABwAAAAcABwAFAAUAAAAAAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAUABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAFAAcABwAFAAUABQAAAAUAAAAHAAcABwAHAAcABwAHAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAHAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABwAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAFAAAAAAAAAAAABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABwAFAAUABQAFAAUAAAAFAAUAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABwAFAAUABQAFAAUABQAAAAUABQAHAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABQAFAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAcABQAFAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAHAAUABQAFAAUABQAFAAUABwAHAAcABwAHAAcABwAHAAUABwAHAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABwAHAAcABwAFAAUABwAHAAcAAAAAAAAAAAAHAAcABQAHAAcABwAHAAcABwAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAcABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAHAAUABQAFAAUABQAFAAUAAAAFAAAABQAAAAAABQAFAAUABQAFAAUABQAFAAcABwAHAAcABwAHAAUABQAFAAUABQAFAAUABQAFAAUAAAAAAAUABQAFAAUABQAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABwAFAAcABwAHAAcABwAFAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAUABQAFAAUABwAHAAUABQAHAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAcABQAFAAcABwAHAAUABwAFAAUABQAHAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABwAHAAcABwAHAAUABQAFAAUABQAFAAUABQAHAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAcABQAFAAUABQAFAAUABQAAAAAAAAAAAAUAAAAAAAAAAAAAAAAABQAAAAAABwAFAAUAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUAAAAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAABQAAAAAAAAAFAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAUABQAHAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAHAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAUABQAFAAUABQAHAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAcABwAFAAUABQAFAAcABwAFAAUABwAHAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAFAAcABwAFAAUABwAHAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAFAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAUABQAAAAAABQAFAAAAAAAAAAAAAAAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAFAAcABwAAAAAAAAAAAAAABwAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAFAAcABwAFAAcABwAAAAcABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAFAAUABQAAAAUABQAAAAAAAAAAAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABwAFAAUABQAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABQAFAAUABQAFAAUABQAFAAUABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAHAAcABQAHAAUABQAAAAAAAAAAAAAAAAAFAAAABwAHAAcABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAHAAcABwAAAAAABwAHAAAAAAAHAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAAAAAAFAAUABQAFAAUABQAFAAAAAAAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAUABQAFAAUABwAHAAUABQAFAAcABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAcABQAFAAUABQAFAAUABwAFAAcABwAFAAcABQAFAAcABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAHAAcABQAFAAUABQAAAAAABwAHAAcABwAFAAUABwAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAHAAUABQAFAAUABQAFAAUABQAHAAcABQAHAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABwAFAAcABwAFAAUABQAFAAUABQAHAAUAAAAAAAAAAAAAAAAAAAAAAAcABwAFAAUABQAFAAcABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAUABQAFAAUABQAHAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAFAAUABQAFAAAAAAAFAAUABwAHAAcABwAFAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABwAHAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABQAFAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAcABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUAAAAHAAUABQAFAAUABQAFAAUABwAFAAUABwAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUAAAAAAAAABQAAAAUABQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAcABwAHAAcAAAAFAAUAAAAHAAcABQAHAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAAAAAAAAAAAAAAAAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAAAAUABQAFAAAAAAAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAAAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAFAAUABQAAAAAABQAFAAUABQAFAAUABQAAAAUABQAAAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAUABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAFAAUABQAFAAUADgAOAA4ADgAOAA4ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAA8ADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAAAAAAAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAMAAwADAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAAAAAAAAAAAAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAKAAoACgAAAAAAAAAAAAsADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwACwAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAADgAOAA4AAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAAAA4ADgAOAA4ADgAOAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAAAA4AAAAOAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAADgAAAAAAAAAAAA4AAAAOAAAAAAAAAAAADgAOAA4AAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAA4ADgAOAA4ADgAOAA4ADgAOAAAADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4ADgAOAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAOAA4ADgAOAA4AAAAAAAAAAAAAAAAAAAAAAA4ADgAOAA4ADgAOAA4ADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAOAA4ADgAOAA4ADgAAAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4ADgAOAA4AAAAAAAAAAAA='; + + /* + * utrie 1.0.2 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var chars$1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Use a lookup table to find the index. + var lookup$1 = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (var i$1 = 0; i$1 < chars$1.length; i$1++) { + lookup$1[chars$1.charCodeAt(i$1)] = i$1; + } + var decode = function (base64) { + var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; + if (base64[base64.length - 1] === '=') { + bufferLength--; + if (base64[base64.length - 2] === '=') { + bufferLength--; + } + } + var buffer = typeof ArrayBuffer !== 'undefined' && + typeof Uint8Array !== 'undefined' && + typeof Uint8Array.prototype.slice !== 'undefined' + ? new ArrayBuffer(bufferLength) + : new Array(bufferLength); + var bytes = Array.isArray(buffer) ? buffer : new Uint8Array(buffer); + for (i = 0; i < len; i += 4) { + encoded1 = lookup$1[base64.charCodeAt(i)]; + encoded2 = lookup$1[base64.charCodeAt(i + 1)]; + encoded3 = lookup$1[base64.charCodeAt(i + 2)]; + encoded4 = lookup$1[base64.charCodeAt(i + 3)]; + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + return buffer; + }; + var polyUint16Array = function (buffer) { + var length = buffer.length; + var bytes = []; + for (var i = 0; i < length; i += 2) { + bytes.push((buffer[i + 1] << 8) | buffer[i]); + } + return bytes; + }; + var polyUint32Array = function (buffer) { + var length = buffer.length; + var bytes = []; + for (var i = 0; i < length; i += 4) { + bytes.push((buffer[i + 3] << 24) | (buffer[i + 2] << 16) | (buffer[i + 1] << 8) | buffer[i]); + } + return bytes; + }; + + /** Shift size for getting the index-2 table offset. */ + var UTRIE2_SHIFT_2 = 5; + /** Shift size for getting the index-1 table offset. */ + var UTRIE2_SHIFT_1 = 6 + 5; + /** + * Shift size for shifting left the index array values. + * Increases possible data size with 16-bit index values at the cost + * of compactability. + * This requires data blocks to be aligned by UTRIE2_DATA_GRANULARITY. + */ + var UTRIE2_INDEX_SHIFT = 2; + /** + * Difference between the two shift sizes, + * for getting an index-1 offset from an index-2 offset. 6=11-5 + */ + var UTRIE2_SHIFT_1_2 = UTRIE2_SHIFT_1 - UTRIE2_SHIFT_2; + /** + * The part of the index-2 table for U+D800..U+DBFF stores values for + * lead surrogate code _units_ not code _points_. + * Values for lead surrogate code _points_ are indexed with this portion of the table. + * Length=32=0x20=0x400>>UTRIE2_SHIFT_2. (There are 1024=0x400 lead surrogates.) + */ + var UTRIE2_LSCP_INDEX_2_OFFSET = 0x10000 >> UTRIE2_SHIFT_2; + /** Number of entries in a data block. 32=0x20 */ + var UTRIE2_DATA_BLOCK_LENGTH = 1 << UTRIE2_SHIFT_2; + /** Mask for getting the lower bits for the in-data-block offset. */ + var UTRIE2_DATA_MASK = UTRIE2_DATA_BLOCK_LENGTH - 1; + var UTRIE2_LSCP_INDEX_2_LENGTH = 0x400 >> UTRIE2_SHIFT_2; + /** Count the lengths of both BMP pieces. 2080=0x820 */ + var UTRIE2_INDEX_2_BMP_LENGTH = UTRIE2_LSCP_INDEX_2_OFFSET + UTRIE2_LSCP_INDEX_2_LENGTH; + /** + * The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820. + * Length 32=0x20 for lead bytes C0..DF, regardless of UTRIE2_SHIFT_2. + */ + var UTRIE2_UTF8_2B_INDEX_2_OFFSET = UTRIE2_INDEX_2_BMP_LENGTH; + var UTRIE2_UTF8_2B_INDEX_2_LENGTH = 0x800 >> 6; /* U+0800 is the first code point after 2-byte UTF-8 */ + /** + * The index-1 table, only used for supplementary code points, at offset 2112=0x840. + * Variable length, for code points up to highStart, where the last single-value range starts. + * Maximum length 512=0x200=0x100000>>UTRIE2_SHIFT_1. + * (For 0x100000 supplementary code points U+10000..U+10ffff.) + * + * The part of the index-2 table for supplementary code points starts + * after this index-1 table. + * + * Both the index-1 table and the following part of the index-2 table + * are omitted completely if there is only BMP data. + */ + var UTRIE2_INDEX_1_OFFSET = UTRIE2_UTF8_2B_INDEX_2_OFFSET + UTRIE2_UTF8_2B_INDEX_2_LENGTH; + /** + * Number of index-1 entries for the BMP. 32=0x20 + * This part of the index-1 table is omitted from the serialized form. + */ + var UTRIE2_OMITTED_BMP_INDEX_1_LENGTH = 0x10000 >> UTRIE2_SHIFT_1; + /** Number of entries in an index-2 block. 64=0x40 */ + var UTRIE2_INDEX_2_BLOCK_LENGTH = 1 << UTRIE2_SHIFT_1_2; + /** Mask for getting the lower bits for the in-index-2-block offset. */ + var UTRIE2_INDEX_2_MASK = UTRIE2_INDEX_2_BLOCK_LENGTH - 1; + var slice16 = function (view, start, end) { + if (view.slice) { + return view.slice(start, end); + } + return new Uint16Array(Array.prototype.slice.call(view, start, end)); + }; + var slice32 = function (view, start, end) { + if (view.slice) { + return view.slice(start, end); + } + return new Uint32Array(Array.prototype.slice.call(view, start, end)); + }; + var createTrieFromBase64 = function (base64, _byteLength) { + var buffer = decode(base64); + var view32 = Array.isArray(buffer) ? polyUint32Array(buffer) : new Uint32Array(buffer); + var view16 = Array.isArray(buffer) ? polyUint16Array(buffer) : new Uint16Array(buffer); + var headerLength = 24; + var index = slice16(view16, headerLength / 2, view32[4] / 2); + var data = view32[5] === 2 + ? slice16(view16, (headerLength + view32[4]) / 2) + : slice32(view32, Math.ceil((headerLength + view32[4]) / 4)); + return new Trie(view32[0], view32[1], view32[2], view32[3], index, data); + }; + var Trie = /** @class */ (function () { + function Trie(initialValue, errorValue, highStart, highValueIndex, index, data) { + this.initialValue = initialValue; + this.errorValue = errorValue; + this.highStart = highStart; + this.highValueIndex = highValueIndex; + this.index = index; + this.data = data; + } + /** + * Get the value for a code point as stored in the Trie. + * + * @param codePoint the code point + * @return the value + */ + Trie.prototype.get = function (codePoint) { + var ix; + if (codePoint >= 0) { + if (codePoint < 0x0d800 || (codePoint > 0x0dbff && codePoint <= 0x0ffff)) { + // Ordinary BMP code point, excluding leading surrogates. + // BMP uses a single level lookup. BMP index starts at offset 0 in the Trie2 index. + // 16 bit data is stored in the index array itself. + ix = this.index[codePoint >> UTRIE2_SHIFT_2]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); + return this.data[ix]; + } + if (codePoint <= 0xffff) { + // Lead Surrogate Code Point. A Separate index section is stored for + // lead surrogate code units and code points. + // The main index has the code unit data. + // For this function, we need the code point data. + // Note: this expression could be refactored for slightly improved efficiency, but + // surrogate code points will be so rare in practice that it's not worth it. + ix = this.index[UTRIE2_LSCP_INDEX_2_OFFSET + ((codePoint - 0xd800) >> UTRIE2_SHIFT_2)]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); + return this.data[ix]; + } + if (codePoint < this.highStart) { + // Supplemental code point, use two-level lookup. + ix = UTRIE2_INDEX_1_OFFSET - UTRIE2_OMITTED_BMP_INDEX_1_LENGTH + (codePoint >> UTRIE2_SHIFT_1); + ix = this.index[ix]; + ix += (codePoint >> UTRIE2_SHIFT_2) & UTRIE2_INDEX_2_MASK; + ix = this.index[ix]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); + return this.data[ix]; + } + if (codePoint <= 0x10ffff) { + return this.data[this.highValueIndex]; + } + } + // Fall through. The code point is outside of the legal range of 0..0x10ffff. + return this.errorValue; + }; + return Trie; + }()); + + /* + * base64-arraybuffer 1.0.2 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Use a lookup table to find the index. + var lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (var i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; + } + + var Prepend = 1; + var CR = 2; + var LF = 3; + var Control = 4; + var Extend = 5; + var SpacingMark = 7; + var L = 8; + var V = 9; + var T = 10; + var LV = 11; + var LVT = 12; + var ZWJ = 13; + var Extended_Pictographic = 14; + var RI = 15; + var toCodePoints = function (str) { + var codePoints = []; + var i = 0; + var length = str.length; + while (i < length) { + var value = str.charCodeAt(i++); + if (value >= 0xd800 && value <= 0xdbff && i < length) { + var extra = str.charCodeAt(i++); + if ((extra & 0xfc00) === 0xdc00) { + codePoints.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000); + } + else { + codePoints.push(value); + i--; + } + } + else { + codePoints.push(value); + } + } + return codePoints; + }; + var fromCodePoint = function () { + var codePoints = []; + for (var _i = 0; _i < arguments.length; _i++) { + codePoints[_i] = arguments[_i]; + } + if (String.fromCodePoint) { + return String.fromCodePoint.apply(String, codePoints); + } + var length = codePoints.length; + if (!length) { + return ''; + } + var codeUnits = []; + var index = -1; + var result = ''; + while (++index < length) { + var codePoint = codePoints[index]; + if (codePoint <= 0xffff) { + codeUnits.push(codePoint); + } + else { + codePoint -= 0x10000; + codeUnits.push((codePoint >> 10) + 0xd800, (codePoint % 0x400) + 0xdc00); + } + if (index + 1 === length || codeUnits.length > 0x4000) { + result += String.fromCharCode.apply(String, codeUnits); + codeUnits.length = 0; + } + } + return result; + }; + var UnicodeTrie = createTrieFromBase64(base64); + var BREAK_NOT_ALLOWED = '×'; + var BREAK_ALLOWED = '÷'; + var codePointToClass = function (codePoint) { return UnicodeTrie.get(codePoint); }; + var _graphemeBreakAtIndex = function (_codePoints, classTypes, index) { + var prevIndex = index - 2; + var prev = classTypes[prevIndex]; + var current = classTypes[index - 1]; + var next = classTypes[index]; + // GB3 Do not break between a CR and LF + if (current === CR && next === LF) { + return BREAK_NOT_ALLOWED; + } + // GB4 Otherwise, break before and after controls. + if (current === CR || current === LF || current === Control) { + return BREAK_ALLOWED; + } + // GB5 + if (next === CR || next === LF || next === Control) { + return BREAK_ALLOWED; + } + // Do not break Hangul syllable sequences. + // GB6 + if (current === L && [L, V, LV, LVT].indexOf(next) !== -1) { + return BREAK_NOT_ALLOWED; + } + // GB7 + if ((current === LV || current === V) && (next === V || next === T)) { + return BREAK_NOT_ALLOWED; + } + // GB8 + if ((current === LVT || current === T) && next === T) { + return BREAK_NOT_ALLOWED; + } + // GB9 Do not break before extending characters or ZWJ. + if (next === ZWJ || next === Extend) { + return BREAK_NOT_ALLOWED; + } + // Do not break before SpacingMarks, or after Prepend characters. + // GB9a + if (next === SpacingMark) { + return BREAK_NOT_ALLOWED; + } + // GB9a + if (current === Prepend) { + return BREAK_NOT_ALLOWED; + } + // GB11 Do not break within emoji modifier sequences or emoji zwj sequences. + if (current === ZWJ && next === Extended_Pictographic) { + while (prev === Extend) { + prev = classTypes[--prevIndex]; + } + if (prev === Extended_Pictographic) { + return BREAK_NOT_ALLOWED; + } + } + // GB12 Do not break within emoji flag sequences. + // That is, do not break between regional indicator (RI) symbols + // if there is an odd number of RI characters before the break point. + if (current === RI && next === RI) { + var countRI = 0; + while (prev === RI) { + countRI++; + prev = classTypes[--prevIndex]; + } + if (countRI % 2 === 0) { + return BREAK_NOT_ALLOWED; + } + } + return BREAK_ALLOWED; + }; + var GraphemeBreaker = function (str) { + var codePoints = toCodePoints(str); + var length = codePoints.length; + var index = 0; + var lastEnd = 0; + var classTypes = codePoints.map(codePointToClass); + return { + next: function () { + if (index >= length) { + return { done: true, value: null }; + } + var graphemeBreak = BREAK_NOT_ALLOWED; + while (index < length && + (graphemeBreak = _graphemeBreakAtIndex(codePoints, classTypes, ++index)) === BREAK_NOT_ALLOWED) { } + if (graphemeBreak !== BREAK_NOT_ALLOWED || index === length) { + var value = fromCodePoint.apply(null, codePoints.slice(lastEnd, index)); + lastEnd = index; + return { value: value, done: false }; + } + return { done: true, value: null }; + }, + }; + }; + var splitGraphemes = function (str) { + var breaker = GraphemeBreaker(str); + var graphemes = []; + var bk; + while (!(bk = breaker.next()).done) { + if (bk.value) { + graphemes.push(bk.value.slice()); + } + } + return graphemes; + }; + + var testRangeBounds = function (document) { + var TEST_HEIGHT = 123; + if (document.createRange) { + var range = document.createRange(); + if (range.getBoundingClientRect) { + var testElement = document.createElement('boundtest'); + testElement.style.height = TEST_HEIGHT + "px"; + testElement.style.display = 'block'; + document.body.appendChild(testElement); + range.selectNode(testElement); + var rangeBounds = range.getBoundingClientRect(); + var rangeHeight = Math.round(rangeBounds.height); + document.body.removeChild(testElement); + if (rangeHeight === TEST_HEIGHT) { + return true; + } + } + } + return false; + }; + var testIOSLineBreak = function (document) { + var testElement = document.createElement('boundtest'); + testElement.style.width = '50px'; + testElement.style.display = 'block'; + testElement.style.fontSize = '12px'; + testElement.style.letterSpacing = '0px'; + testElement.style.wordSpacing = '0px'; + document.body.appendChild(testElement); + var range = document.createRange(); + testElement.innerHTML = typeof ''.repeat === 'function' ? '👨'.repeat(10) : ''; + var node = testElement.firstChild; + var textList = toCodePoints$1(node.data).map(function (i) { return fromCodePoint$1(i); }); + var offset = 0; + var prev = {}; + // ios 13 does not handle range getBoundingClientRect line changes correctly #2177 + var supports = textList.every(function (text, i) { + range.setStart(node, offset); + range.setEnd(node, offset + text.length); + var rect = range.getBoundingClientRect(); + offset += text.length; + var boundAhead = rect.x > prev.x || rect.y > prev.y; + prev = rect; + if (i === 0) { + return true; + } + return boundAhead; + }); + document.body.removeChild(testElement); + return supports; + }; + var testCORS = function () { return typeof new Image().crossOrigin !== 'undefined'; }; + var testResponseType = function () { return typeof new XMLHttpRequest().responseType === 'string'; }; + var testSVG = function (document) { + var img = new Image(); + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + if (!ctx) { + return false; + } + img.src = "data:image/svg+xml,"; + try { + ctx.drawImage(img, 0, 0); + canvas.toDataURL(); + } + catch (e) { + return false; + } + return true; + }; + var isGreenPixel = function (data) { + return data[0] === 0 && data[1] === 255 && data[2] === 0 && data[3] === 255; + }; + var testForeignObject = function (document) { + var canvas = document.createElement('canvas'); + var size = 100; + canvas.width = size; + canvas.height = size; + var ctx = canvas.getContext('2d'); + if (!ctx) { + return Promise.reject(false); + } + ctx.fillStyle = 'rgb(0, 255, 0)'; + ctx.fillRect(0, 0, size, size); + var img = new Image(); + var greenImageSrc = canvas.toDataURL(); + img.src = greenImageSrc; + var svg = createForeignObjectSVG(size, size, 0, 0, img); + ctx.fillStyle = 'red'; + ctx.fillRect(0, 0, size, size); + return loadSerializedSVG$1(svg) + .then(function (img) { + ctx.drawImage(img, 0, 0); + var data = ctx.getImageData(0, 0, size, size).data; + ctx.fillStyle = 'red'; + ctx.fillRect(0, 0, size, size); + var node = document.createElement('div'); + node.style.backgroundImage = "url(" + greenImageSrc + ")"; + node.style.height = size + "px"; + // Firefox 55 does not render inline tags + return isGreenPixel(data) + ? loadSerializedSVG$1(createForeignObjectSVG(size, size, 0, 0, node)) + : Promise.reject(false); + }) + .then(function (img) { + ctx.drawImage(img, 0, 0); + // Edge does not render background-images + return isGreenPixel(ctx.getImageData(0, 0, size, size).data); + }) + .catch(function () { return false; }); + }; + var createForeignObjectSVG = function (width, height, x, y, node) { + var xmlns = 'http://www.w3.org/2000/svg'; + var svg = document.createElementNS(xmlns, 'svg'); + var foreignObject = document.createElementNS(xmlns, 'foreignObject'); + svg.setAttributeNS(null, 'width', width.toString()); + svg.setAttributeNS(null, 'height', height.toString()); + foreignObject.setAttributeNS(null, 'width', '100%'); + foreignObject.setAttributeNS(null, 'height', '100%'); + foreignObject.setAttributeNS(null, 'x', x.toString()); + foreignObject.setAttributeNS(null, 'y', y.toString()); + foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true'); + svg.appendChild(foreignObject); + foreignObject.appendChild(node); + return svg; + }; + var loadSerializedSVG$1 = function (svg) { + return new Promise(function (resolve, reject) { + var img = new Image(); + img.onload = function () { return resolve(img); }; + img.onerror = reject; + img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(new XMLSerializer().serializeToString(svg)); + }); + }; + var FEATURES = { + get SUPPORT_RANGE_BOUNDS() { + var value = testRangeBounds(document); + Object.defineProperty(FEATURES, 'SUPPORT_RANGE_BOUNDS', { value: value }); + return value; + }, + get SUPPORT_WORD_BREAKING() { + var value = FEATURES.SUPPORT_RANGE_BOUNDS && testIOSLineBreak(document); + Object.defineProperty(FEATURES, 'SUPPORT_WORD_BREAKING', { value: value }); + return value; + }, + get SUPPORT_SVG_DRAWING() { + var value = testSVG(document); + Object.defineProperty(FEATURES, 'SUPPORT_SVG_DRAWING', { value: value }); + return value; + }, + get SUPPORT_FOREIGNOBJECT_DRAWING() { + var value = typeof Array.from === 'function' && typeof window.fetch === 'function' + ? testForeignObject(document) + : Promise.resolve(false); + Object.defineProperty(FEATURES, 'SUPPORT_FOREIGNOBJECT_DRAWING', { value: value }); + return value; + }, + get SUPPORT_CORS_IMAGES() { + var value = testCORS(); + Object.defineProperty(FEATURES, 'SUPPORT_CORS_IMAGES', { value: value }); + return value; + }, + get SUPPORT_RESPONSE_TYPE() { + var value = testResponseType(); + Object.defineProperty(FEATURES, 'SUPPORT_RESPONSE_TYPE', { value: value }); + return value; + }, + get SUPPORT_CORS_XHR() { + var value = 'withCredentials' in new XMLHttpRequest(); + Object.defineProperty(FEATURES, 'SUPPORT_CORS_XHR', { value: value }); + return value; + }, + get SUPPORT_NATIVE_TEXT_SEGMENTATION() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var value = !!(typeof Intl !== 'undefined' && Intl.Segmenter); + Object.defineProperty(FEATURES, 'SUPPORT_NATIVE_TEXT_SEGMENTATION', { value: value }); + return value; + } + }; + + var TextBounds = /** @class */ (function () { + function TextBounds(text, bounds) { + this.text = text; + this.bounds = bounds; + } + return TextBounds; + }()); + var parseTextBounds = function (context, value, styles, node) { + var textList = breakText(value, styles); + var textBounds = []; + var offset = 0; + textList.forEach(function (text) { + if (styles.textDecorationLine.length || text.trim().length > 0) { + if (FEATURES.SUPPORT_RANGE_BOUNDS) { + var clientRects = createRange(node, offset, text.length).getClientRects(); + if (clientRects.length > 1) { + var subSegments = segmentGraphemes(text); + var subOffset_1 = 0; + subSegments.forEach(function (subSegment) { + textBounds.push(new TextBounds(subSegment, Bounds.fromDOMRectList(context, createRange(node, subOffset_1 + offset, subSegment.length).getClientRects()))); + subOffset_1 += subSegment.length; + }); + } + else { + textBounds.push(new TextBounds(text, Bounds.fromDOMRectList(context, clientRects))); + } + } + else { + var replacementNode = node.splitText(text.length); + textBounds.push(new TextBounds(text, getWrapperBounds(context, node))); + node = replacementNode; + } + } + else if (!FEATURES.SUPPORT_RANGE_BOUNDS) { + node = node.splitText(text.length); + } + offset += text.length; + }); + return textBounds; + }; + var getWrapperBounds = function (context, node) { + var ownerDocument = node.ownerDocument; + if (ownerDocument) { + var wrapper = ownerDocument.createElement('html2canvaswrapper'); + wrapper.appendChild(node.cloneNode(true)); + var parentNode = node.parentNode; + if (parentNode) { + parentNode.replaceChild(wrapper, node); + var bounds = parseBounds(context, wrapper); + if (wrapper.firstChild) { + parentNode.replaceChild(wrapper.firstChild, wrapper); + } + return bounds; + } + } + return Bounds.EMPTY; + }; + var createRange = function (node, offset, length) { + var ownerDocument = node.ownerDocument; + if (!ownerDocument) { + throw new Error('Node has no owner document'); + } + var range = ownerDocument.createRange(); + range.setStart(node, offset); + range.setEnd(node, offset + length); + return range; + }; + var segmentGraphemes = function (value) { + if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var segmenter = new Intl.Segmenter(void 0, { granularity: 'grapheme' }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return Array.from(segmenter.segment(value)).map(function (segment) { return segment.segment; }); + } + return splitGraphemes(value); + }; + var segmentWords = function (value, styles) { + if (FEATURES.SUPPORT_NATIVE_TEXT_SEGMENTATION) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var segmenter = new Intl.Segmenter(void 0, { + granularity: 'word' + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return Array.from(segmenter.segment(value)).map(function (segment) { return segment.segment; }); + } + return breakWords(value, styles); + }; + var breakText = function (value, styles) { + return styles.letterSpacing !== 0 ? segmentGraphemes(value) : segmentWords(value, styles); + }; + // https://drafts.csswg.org/css-text/#word-separator + var wordSeparators = [0x0020, 0x00a0, 0x1361, 0x10100, 0x10101, 0x1039, 0x1091]; + var breakWords = function (str, styles) { + var breaker = LineBreaker(str, { + lineBreak: styles.lineBreak, + wordBreak: styles.overflowWrap === "break-word" /* BREAK_WORD */ ? 'break-word' : styles.wordBreak + }); + var words = []; + var bk; + var _loop_1 = function () { + if (bk.value) { + var value = bk.value.slice(); + var codePoints = toCodePoints$1(value); + var word_1 = ''; + codePoints.forEach(function (codePoint) { + if (wordSeparators.indexOf(codePoint) === -1) { + word_1 += fromCodePoint$1(codePoint); + } + else { + if (word_1.length) { + words.push(word_1); + } + words.push(fromCodePoint$1(codePoint)); + word_1 = ''; + } + }); + if (word_1.length) { + words.push(word_1); + } + } + }; + while (!(bk = breaker.next()).done) { + _loop_1(); + } + return words; + }; + + var TextContainer = /** @class */ (function () { + function TextContainer(context, node, styles) { + this.text = transform(node.data, styles.textTransform); + this.textBounds = parseTextBounds(context, this.text, styles, node); + } + return TextContainer; + }()); + var transform = function (text, transform) { + switch (transform) { + case 1 /* LOWERCASE */: + return text.toLowerCase(); + case 3 /* CAPITALIZE */: + return text.replace(CAPITALIZE, capitalize); + case 2 /* UPPERCASE */: + return text.toUpperCase(); + default: + return text; + } + }; + var CAPITALIZE = /(^|\s|:|-|\(|\))([a-z])/g; + var capitalize = function (m, p1, p2) { + if (m.length > 0) { + return p1 + p2.toUpperCase(); + } + return m; + }; + + var ImageElementContainer = /** @class */ (function (_super) { + __extends(ImageElementContainer, _super); + function ImageElementContainer(context, img) { + var _this = _super.call(this, context, img) || this; + _this.src = img.currentSrc || img.src; + _this.intrinsicWidth = img.naturalWidth; + _this.intrinsicHeight = img.naturalHeight; + _this.context.cache.addImage(_this.src); + return _this; + } + return ImageElementContainer; + }(ElementContainer)); + + var CanvasElementContainer = /** @class */ (function (_super) { + __extends(CanvasElementContainer, _super); + function CanvasElementContainer(context, canvas) { + var _this = _super.call(this, context, canvas) || this; + _this.canvas = canvas; + _this.intrinsicWidth = canvas.width; + _this.intrinsicHeight = canvas.height; + return _this; + } + return CanvasElementContainer; + }(ElementContainer)); + + var SVGElementContainer = /** @class */ (function (_super) { + __extends(SVGElementContainer, _super); + function SVGElementContainer(context, img) { + var _this = _super.call(this, context, img) || this; + var s = new XMLSerializer(); + var bounds = parseBounds(context, img); + img.setAttribute('width', bounds.width + "px"); + img.setAttribute('height', bounds.height + "px"); + _this.svg = "data:image/svg+xml," + encodeURIComponent(s.serializeToString(img)); + _this.intrinsicWidth = img.width.baseVal.value; + _this.intrinsicHeight = img.height.baseVal.value; + _this.context.cache.addImage(_this.svg); + return _this; + } + return SVGElementContainer; + }(ElementContainer)); + + var LIElementContainer = /** @class */ (function (_super) { + __extends(LIElementContainer, _super); + function LIElementContainer(context, element) { + var _this = _super.call(this, context, element) || this; + _this.value = element.value; + return _this; + } + return LIElementContainer; + }(ElementContainer)); + + var OLElementContainer = /** @class */ (function (_super) { + __extends(OLElementContainer, _super); + function OLElementContainer(context, element) { + var _this = _super.call(this, context, element) || this; + _this.start = element.start; + _this.reversed = typeof element.reversed === 'boolean' && element.reversed === true; + return _this; + } + return OLElementContainer; + }(ElementContainer)); + + var CHECKBOX_BORDER_RADIUS = [ + { + type: 15 /* DIMENSION_TOKEN */, + flags: 0, + unit: 'px', + number: 3 + } + ]; + var RADIO_BORDER_RADIUS = [ + { + type: 16 /* PERCENTAGE_TOKEN */, + flags: 0, + number: 50 + } + ]; + var reformatInputBounds = function (bounds) { + if (bounds.width > bounds.height) { + return new Bounds(bounds.left + (bounds.width - bounds.height) / 2, bounds.top, bounds.height, bounds.height); + } + else if (bounds.width < bounds.height) { + return new Bounds(bounds.left, bounds.top + (bounds.height - bounds.width) / 2, bounds.width, bounds.width); + } + return bounds; + }; + var getInputValue = function (node) { + var value = node.type === PASSWORD ? new Array(node.value.length + 1).join('\u2022') : node.value; + return value.length === 0 ? node.placeholder || '' : value; + }; + var CHECKBOX = 'checkbox'; + var RADIO = 'radio'; + var PASSWORD = 'password'; + var INPUT_COLOR = 0x2a2a2aff; + var InputElementContainer = /** @class */ (function (_super) { + __extends(InputElementContainer, _super); + function InputElementContainer(context, input) { + var _this = _super.call(this, context, input) || this; + _this.type = input.type.toLowerCase(); + _this.checked = input.checked; + _this.value = getInputValue(input); + if (_this.type === CHECKBOX || _this.type === RADIO) { + _this.styles.backgroundColor = 0xdededeff; + _this.styles.borderTopColor = + _this.styles.borderRightColor = + _this.styles.borderBottomColor = + _this.styles.borderLeftColor = + 0xa5a5a5ff; + _this.styles.borderTopWidth = + _this.styles.borderRightWidth = + _this.styles.borderBottomWidth = + _this.styles.borderLeftWidth = + 1; + _this.styles.borderTopStyle = + _this.styles.borderRightStyle = + _this.styles.borderBottomStyle = + _this.styles.borderLeftStyle = + 1 /* SOLID */; + _this.styles.backgroundClip = [0 /* BORDER_BOX */]; + _this.styles.backgroundOrigin = [0 /* BORDER_BOX */]; + _this.bounds = reformatInputBounds(_this.bounds); + } + switch (_this.type) { + case CHECKBOX: + _this.styles.borderTopRightRadius = + _this.styles.borderTopLeftRadius = + _this.styles.borderBottomRightRadius = + _this.styles.borderBottomLeftRadius = + CHECKBOX_BORDER_RADIUS; + break; + case RADIO: + _this.styles.borderTopRightRadius = + _this.styles.borderTopLeftRadius = + _this.styles.borderBottomRightRadius = + _this.styles.borderBottomLeftRadius = + RADIO_BORDER_RADIUS; + break; + } + return _this; + } + return InputElementContainer; + }(ElementContainer)); + + var SelectElementContainer = /** @class */ (function (_super) { + __extends(SelectElementContainer, _super); + function SelectElementContainer(context, element) { + var _this = _super.call(this, context, element) || this; + var option = element.options[element.selectedIndex || 0]; + _this.value = option ? option.text || '' : ''; + return _this; + } + return SelectElementContainer; + }(ElementContainer)); + + var TextareaElementContainer = /** @class */ (function (_super) { + __extends(TextareaElementContainer, _super); + function TextareaElementContainer(context, element) { + var _this = _super.call(this, context, element) || this; + _this.value = element.value; + return _this; + } + return TextareaElementContainer; + }(ElementContainer)); + + var IFrameElementContainer = /** @class */ (function (_super) { + __extends(IFrameElementContainer, _super); + function IFrameElementContainer(context, iframe) { + var _this = _super.call(this, context, iframe) || this; + _this.src = iframe.src; + _this.width = parseInt(iframe.width, 10) || 0; + _this.height = parseInt(iframe.height, 10) || 0; + _this.backgroundColor = _this.styles.backgroundColor; + try { + if (iframe.contentWindow && + iframe.contentWindow.document && + iframe.contentWindow.document.documentElement) { + _this.tree = parseTree(context, iframe.contentWindow.document.documentElement); + // http://www.w3.org/TR/css3-background/#special-backgrounds + var documentBackgroundColor = iframe.contentWindow.document.documentElement + ? parseColor(context, getComputedStyle(iframe.contentWindow.document.documentElement).backgroundColor) + : COLORS.TRANSPARENT; + var bodyBackgroundColor = iframe.contentWindow.document.body + ? parseColor(context, getComputedStyle(iframe.contentWindow.document.body).backgroundColor) + : COLORS.TRANSPARENT; + _this.backgroundColor = isTransparent(documentBackgroundColor) + ? isTransparent(bodyBackgroundColor) + ? _this.styles.backgroundColor + : bodyBackgroundColor + : documentBackgroundColor; + } + } + catch (e) { } + return _this; + } + return IFrameElementContainer; + }(ElementContainer)); + + var LIST_OWNERS = ['OL', 'UL', 'MENU']; + var parseNodeTree = function (context, node, parent, root) { + for (var childNode = node.firstChild, nextNode = void 0; childNode; childNode = nextNode) { + nextNode = childNode.nextSibling; + if (isTextNode(childNode) && childNode.data.trim().length > 0) { + parent.textNodes.push(new TextContainer(context, childNode, parent.styles)); + } + else if (isElementNode(childNode)) { + if (isSlotElement(childNode) && childNode.assignedNodes) { + childNode.assignedNodes().forEach(function (childNode) { return parseNodeTree(context, childNode, parent, root); }); + } + else { + var container = createContainer(context, childNode); + if (container.styles.isVisible()) { + if (createsRealStackingContext(childNode, container, root)) { + container.flags |= 4 /* CREATES_REAL_STACKING_CONTEXT */; + } + else if (createsStackingContext(container.styles)) { + container.flags |= 2 /* CREATES_STACKING_CONTEXT */; + } + if (LIST_OWNERS.indexOf(childNode.tagName) !== -1) { + container.flags |= 8 /* IS_LIST_OWNER */; + } + parent.elements.push(container); + childNode.slot; + if (childNode.shadowRoot) { + parseNodeTree(context, childNode.shadowRoot, container, root); + } + else if (!isTextareaElement(childNode) && + !isSVGElement(childNode) && + !isSelectElement(childNode)) { + parseNodeTree(context, childNode, container, root); + } + } + } + } + } + }; + var createContainer = function (context, element) { + if (isImageElement(element)) { + return new ImageElementContainer(context, element); + } + if (isCanvasElement(element)) { + return new CanvasElementContainer(context, element); + } + if (isSVGElement(element)) { + return new SVGElementContainer(context, element); + } + if (isLIElement(element)) { + return new LIElementContainer(context, element); + } + if (isOLElement(element)) { + return new OLElementContainer(context, element); + } + if (isInputElement(element)) { + return new InputElementContainer(context, element); + } + if (isSelectElement(element)) { + return new SelectElementContainer(context, element); + } + if (isTextareaElement(element)) { + return new TextareaElementContainer(context, element); + } + if (isIFrameElement(element)) { + return new IFrameElementContainer(context, element); + } + return new ElementContainer(context, element); + }; + var parseTree = function (context, element) { + var container = createContainer(context, element); + container.flags |= 4 /* CREATES_REAL_STACKING_CONTEXT */; + parseNodeTree(context, element, container, container); + return container; + }; + var createsRealStackingContext = function (node, container, root) { + return (container.styles.isPositionedWithZIndex() || + container.styles.opacity < 1 || + container.styles.isTransformed() || + (isBodyElement(node) && root.styles.isTransparent())); + }; + var createsStackingContext = function (styles) { return styles.isPositioned() || styles.isFloating(); }; + var isTextNode = function (node) { return node.nodeType === Node.TEXT_NODE; }; + var isElementNode = function (node) { return node.nodeType === Node.ELEMENT_NODE; }; + var isHTMLElementNode = function (node) { + return isElementNode(node) && typeof node.style !== 'undefined' && !isSVGElementNode(node); + }; + var isSVGElementNode = function (element) { + return typeof element.className === 'object'; + }; + var isLIElement = function (node) { return node.tagName === 'LI'; }; + var isOLElement = function (node) { return node.tagName === 'OL'; }; + var isInputElement = function (node) { return node.tagName === 'INPUT'; }; + var isHTMLElement = function (node) { return node.tagName === 'HTML'; }; + var isSVGElement = function (node) { return node.tagName === 'svg'; }; + var isBodyElement = function (node) { return node.tagName === 'BODY'; }; + var isCanvasElement = function (node) { return node.tagName === 'CANVAS'; }; + var isVideoElement = function (node) { return node.tagName === 'VIDEO'; }; + var isImageElement = function (node) { return node.tagName === 'IMG'; }; + var isIFrameElement = function (node) { return node.tagName === 'IFRAME'; }; + var isStyleElement = function (node) { return node.tagName === 'STYLE'; }; + var isScriptElement = function (node) { return node.tagName === 'SCRIPT'; }; + var isTextareaElement = function (node) { return node.tagName === 'TEXTAREA'; }; + var isSelectElement = function (node) { return node.tagName === 'SELECT'; }; + var isSlotElement = function (node) { return node.tagName === 'SLOT'; }; + // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name + var isCustomElement = function (node) { return node.tagName.indexOf('-') > 0; }; + + var CounterState = /** @class */ (function () { + function CounterState() { + this.counters = {}; + } + CounterState.prototype.getCounterValue = function (name) { + var counter = this.counters[name]; + if (counter && counter.length) { + return counter[counter.length - 1]; + } + return 1; + }; + CounterState.prototype.getCounterValues = function (name) { + var counter = this.counters[name]; + return counter ? counter : []; + }; + CounterState.prototype.pop = function (counters) { + var _this = this; + counters.forEach(function (counter) { return _this.counters[counter].pop(); }); + }; + CounterState.prototype.parse = function (style) { + var _this = this; + var counterIncrement = style.counterIncrement; + var counterReset = style.counterReset; + var canReset = true; + if (counterIncrement !== null) { + counterIncrement.forEach(function (entry) { + var counter = _this.counters[entry.counter]; + if (counter && entry.increment !== 0) { + canReset = false; + if (!counter.length) { + counter.push(1); + } + counter[Math.max(0, counter.length - 1)] += entry.increment; + } + }); + } + var counterNames = []; + if (canReset) { + counterReset.forEach(function (entry) { + var counter = _this.counters[entry.counter]; + counterNames.push(entry.counter); + if (!counter) { + counter = _this.counters[entry.counter] = []; + } + counter.push(entry.reset); + }); + } + return counterNames; + }; + return CounterState; + }()); + var ROMAN_UPPER = { + integers: [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1], + values: ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'] + }; + var ARMENIAN = { + integers: [ + 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 90, 80, 70, + 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 + ], + values: [ + 'Ք', + 'Փ', + 'Ւ', + 'Ց', + 'Ր', + 'Տ', + 'Վ', + 'Ս', + 'Ռ', + 'Ջ', + 'Պ', + 'Չ', + 'Ո', + 'Շ', + 'Ն', + 'Յ', + 'Մ', + 'Ճ', + 'Ղ', + 'Ձ', + 'Հ', + 'Կ', + 'Ծ', + 'Խ', + 'Լ', + 'Ի', + 'Ժ', + 'Թ', + 'Ը', + 'Է', + 'Զ', + 'Ե', + 'Դ', + 'Գ', + 'Բ', + 'Ա' + ] + }; + var HEBREW = { + integers: [ + 10000, 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 400, 300, 200, 100, 90, 80, 70, 60, 50, 40, 30, 20, + 19, 18, 17, 16, 15, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 + ], + values: [ + 'י׳', + 'ט׳', + 'ח׳', + 'ז׳', + 'ו׳', + 'ה׳', + 'ד׳', + 'ג׳', + 'ב׳', + 'א׳', + 'ת', + 'ש', + 'ר', + 'ק', + 'צ', + 'פ', + 'ע', + 'ס', + 'נ', + 'מ', + 'ל', + 'כ', + 'יט', + 'יח', + 'יז', + 'טז', + 'טו', + 'י', + 'ט', + 'ח', + 'ז', + 'ו', + 'ה', + 'ד', + 'ג', + 'ב', + 'א' + ] + }; + var GEORGIAN = { + integers: [ + 10000, 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 90, + 80, 70, 60, 50, 40, 30, 20, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 + ], + values: [ + 'ჵ', + 'ჰ', + 'ჯ', + 'ჴ', + 'ხ', + 'ჭ', + 'წ', + 'ძ', + 'ც', + 'ჩ', + 'შ', + 'ყ', + 'ღ', + 'ქ', + 'ფ', + 'ჳ', + 'ტ', + 'ს', + 'რ', + 'ჟ', + 'პ', + 'ო', + 'ჲ', + 'ნ', + 'მ', + 'ლ', + 'კ', + 'ი', + 'თ', + 'ჱ', + 'ზ', + 'ვ', + 'ე', + 'დ', + 'გ', + 'ბ', + 'ა' + ] + }; + var createAdditiveCounter = function (value, min, max, symbols, fallback, suffix) { + if (value < min || value > max) { + return createCounterText(value, fallback, suffix.length > 0); + } + return (symbols.integers.reduce(function (string, integer, index) { + while (value >= integer) { + value -= integer; + string += symbols.values[index]; + } + return string; + }, '') + suffix); + }; + var createCounterStyleWithSymbolResolver = function (value, codePointRangeLength, isNumeric, resolver) { + var string = ''; + do { + if (!isNumeric) { + value--; + } + string = resolver(value) + string; + value /= codePointRangeLength; + } while (value * codePointRangeLength >= codePointRangeLength); + return string; + }; + var createCounterStyleFromRange = function (value, codePointRangeStart, codePointRangeEnd, isNumeric, suffix) { + var codePointRangeLength = codePointRangeEnd - codePointRangeStart + 1; + return ((value < 0 ? '-' : '') + + (createCounterStyleWithSymbolResolver(Math.abs(value), codePointRangeLength, isNumeric, function (codePoint) { + return fromCodePoint$1(Math.floor(codePoint % codePointRangeLength) + codePointRangeStart); + }) + + suffix)); + }; + var createCounterStyleFromSymbols = function (value, symbols, suffix) { + if (suffix === void 0) { suffix = '. '; } + var codePointRangeLength = symbols.length; + return (createCounterStyleWithSymbolResolver(Math.abs(value), codePointRangeLength, false, function (codePoint) { return symbols[Math.floor(codePoint % codePointRangeLength)]; }) + suffix); + }; + var CJK_ZEROS = 1 << 0; + var CJK_TEN_COEFFICIENTS = 1 << 1; + var CJK_TEN_HIGH_COEFFICIENTS = 1 << 2; + var CJK_HUNDRED_COEFFICIENTS = 1 << 3; + var createCJKCounter = function (value, numbers, multipliers, negativeSign, suffix, flags) { + if (value < -9999 || value > 9999) { + return createCounterText(value, 4 /* CJK_DECIMAL */, suffix.length > 0); + } + var tmp = Math.abs(value); + var string = suffix; + if (tmp === 0) { + return numbers[0] + string; + } + for (var digit = 0; tmp > 0 && digit <= 4; digit++) { + var coefficient = tmp % 10; + if (coefficient === 0 && contains(flags, CJK_ZEROS) && string !== '') { + string = numbers[coefficient] + string; + } + else if (coefficient > 1 || + (coefficient === 1 && digit === 0) || + (coefficient === 1 && digit === 1 && contains(flags, CJK_TEN_COEFFICIENTS)) || + (coefficient === 1 && digit === 1 && contains(flags, CJK_TEN_HIGH_COEFFICIENTS) && value > 100) || + (coefficient === 1 && digit > 1 && contains(flags, CJK_HUNDRED_COEFFICIENTS))) { + string = numbers[coefficient] + (digit > 0 ? multipliers[digit - 1] : '') + string; + } + else if (coefficient === 1 && digit > 0) { + string = multipliers[digit - 1] + string; + } + tmp = Math.floor(tmp / 10); + } + return (value < 0 ? negativeSign : '') + string; + }; + var CHINESE_INFORMAL_MULTIPLIERS = '十百千萬'; + var CHINESE_FORMAL_MULTIPLIERS = '拾佰仟萬'; + var JAPANESE_NEGATIVE = 'マイナス'; + var KOREAN_NEGATIVE = '마이너스'; + var createCounterText = function (value, type, appendSuffix) { + var defaultSuffix = appendSuffix ? '. ' : ''; + var cjkSuffix = appendSuffix ? '、' : ''; + var koreanSuffix = appendSuffix ? ', ' : ''; + var spaceSuffix = appendSuffix ? ' ' : ''; + switch (type) { + case 0 /* DISC */: + return '•' + spaceSuffix; + case 1 /* CIRCLE */: + return '◦' + spaceSuffix; + case 2 /* SQUARE */: + return '◾' + spaceSuffix; + case 5 /* DECIMAL_LEADING_ZERO */: + var string = createCounterStyleFromRange(value, 48, 57, true, defaultSuffix); + return string.length < 4 ? "0" + string : string; + case 4 /* CJK_DECIMAL */: + return createCounterStyleFromSymbols(value, '〇一二三四五六七八九', cjkSuffix); + case 6 /* LOWER_ROMAN */: + return createAdditiveCounter(value, 1, 3999, ROMAN_UPPER, 3 /* DECIMAL */, defaultSuffix).toLowerCase(); + case 7 /* UPPER_ROMAN */: + return createAdditiveCounter(value, 1, 3999, ROMAN_UPPER, 3 /* DECIMAL */, defaultSuffix); + case 8 /* LOWER_GREEK */: + return createCounterStyleFromRange(value, 945, 969, false, defaultSuffix); + case 9 /* LOWER_ALPHA */: + return createCounterStyleFromRange(value, 97, 122, false, defaultSuffix); + case 10 /* UPPER_ALPHA */: + return createCounterStyleFromRange(value, 65, 90, false, defaultSuffix); + case 11 /* ARABIC_INDIC */: + return createCounterStyleFromRange(value, 1632, 1641, true, defaultSuffix); + case 12 /* ARMENIAN */: + case 49 /* UPPER_ARMENIAN */: + return createAdditiveCounter(value, 1, 9999, ARMENIAN, 3 /* DECIMAL */, defaultSuffix); + case 35 /* LOWER_ARMENIAN */: + return createAdditiveCounter(value, 1, 9999, ARMENIAN, 3 /* DECIMAL */, defaultSuffix).toLowerCase(); + case 13 /* BENGALI */: + return createCounterStyleFromRange(value, 2534, 2543, true, defaultSuffix); + case 14 /* CAMBODIAN */: + case 30 /* KHMER */: + return createCounterStyleFromRange(value, 6112, 6121, true, defaultSuffix); + case 15 /* CJK_EARTHLY_BRANCH */: + return createCounterStyleFromSymbols(value, '子丑寅卯辰巳午未申酉戌亥', cjkSuffix); + case 16 /* CJK_HEAVENLY_STEM */: + return createCounterStyleFromSymbols(value, '甲乙丙丁戊己庚辛壬癸', cjkSuffix); + case 17 /* CJK_IDEOGRAPHIC */: + case 48 /* TRAD_CHINESE_INFORMAL */: + return createCJKCounter(value, '零一二三四五六七八九', CHINESE_INFORMAL_MULTIPLIERS, '負', cjkSuffix, CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS); + case 47 /* TRAD_CHINESE_FORMAL */: + return createCJKCounter(value, '零壹貳參肆伍陸柒捌玖', CHINESE_FORMAL_MULTIPLIERS, '負', cjkSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS); + case 42 /* SIMP_CHINESE_INFORMAL */: + return createCJKCounter(value, '零一二三四五六七八九', CHINESE_INFORMAL_MULTIPLIERS, '负', cjkSuffix, CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS); + case 41 /* SIMP_CHINESE_FORMAL */: + return createCJKCounter(value, '零壹贰叁肆伍陆柒捌玖', CHINESE_FORMAL_MULTIPLIERS, '负', cjkSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS | CJK_HUNDRED_COEFFICIENTS); + case 26 /* JAPANESE_INFORMAL */: + return createCJKCounter(value, '〇一二三四五六七八九', '十百千万', JAPANESE_NEGATIVE, cjkSuffix, 0); + case 25 /* JAPANESE_FORMAL */: + return createCJKCounter(value, '零壱弐参四伍六七八九', '拾百千万', JAPANESE_NEGATIVE, cjkSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS); + case 31 /* KOREAN_HANGUL_FORMAL */: + return createCJKCounter(value, '영일이삼사오육칠팔구', '십백천만', KOREAN_NEGATIVE, koreanSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS); + case 33 /* KOREAN_HANJA_INFORMAL */: + return createCJKCounter(value, '零一二三四五六七八九', '十百千萬', KOREAN_NEGATIVE, koreanSuffix, 0); + case 32 /* KOREAN_HANJA_FORMAL */: + return createCJKCounter(value, '零壹貳參四五六七八九', '拾百千', KOREAN_NEGATIVE, koreanSuffix, CJK_ZEROS | CJK_TEN_COEFFICIENTS | CJK_TEN_HIGH_COEFFICIENTS); + case 18 /* DEVANAGARI */: + return createCounterStyleFromRange(value, 0x966, 0x96f, true, defaultSuffix); + case 20 /* GEORGIAN */: + return createAdditiveCounter(value, 1, 19999, GEORGIAN, 3 /* DECIMAL */, defaultSuffix); + case 21 /* GUJARATI */: + return createCounterStyleFromRange(value, 0xae6, 0xaef, true, defaultSuffix); + case 22 /* GURMUKHI */: + return createCounterStyleFromRange(value, 0xa66, 0xa6f, true, defaultSuffix); + case 22 /* HEBREW */: + return createAdditiveCounter(value, 1, 10999, HEBREW, 3 /* DECIMAL */, defaultSuffix); + case 23 /* HIRAGANA */: + return createCounterStyleFromSymbols(value, 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわゐゑをん'); + case 24 /* HIRAGANA_IROHA */: + return createCounterStyleFromSymbols(value, 'いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてあさきゆめみしゑひもせす'); + case 27 /* KANNADA */: + return createCounterStyleFromRange(value, 0xce6, 0xcef, true, defaultSuffix); + case 28 /* KATAKANA */: + return createCounterStyleFromSymbols(value, 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヰヱヲン', cjkSuffix); + case 29 /* KATAKANA_IROHA */: + return createCounterStyleFromSymbols(value, 'イロハニホヘトチリヌルヲワカヨタレソツネナラムウヰノオクヤマケフコエテアサキユメミシヱヒモセス', cjkSuffix); + case 34 /* LAO */: + return createCounterStyleFromRange(value, 0xed0, 0xed9, true, defaultSuffix); + case 37 /* MONGOLIAN */: + return createCounterStyleFromRange(value, 0x1810, 0x1819, true, defaultSuffix); + case 38 /* MYANMAR */: + return createCounterStyleFromRange(value, 0x1040, 0x1049, true, defaultSuffix); + case 39 /* ORIYA */: + return createCounterStyleFromRange(value, 0xb66, 0xb6f, true, defaultSuffix); + case 40 /* PERSIAN */: + return createCounterStyleFromRange(value, 0x6f0, 0x6f9, true, defaultSuffix); + case 43 /* TAMIL */: + return createCounterStyleFromRange(value, 0xbe6, 0xbef, true, defaultSuffix); + case 44 /* TELUGU */: + return createCounterStyleFromRange(value, 0xc66, 0xc6f, true, defaultSuffix); + case 45 /* THAI */: + return createCounterStyleFromRange(value, 0xe50, 0xe59, true, defaultSuffix); + case 46 /* TIBETAN */: + return createCounterStyleFromRange(value, 0xf20, 0xf29, true, defaultSuffix); + case 3 /* DECIMAL */: + default: + return createCounterStyleFromRange(value, 48, 57, true, defaultSuffix); + } + }; + + var IGNORE_ATTRIBUTE = 'data-html2canvas-ignore'; + var DocumentCloner = /** @class */ (function () { + function DocumentCloner(context, element, options) { + this.context = context; + this.options = options; + this.scrolledElements = []; + this.referenceElement = element; + this.counters = new CounterState(); + this.quoteDepth = 0; + if (!element.ownerDocument) { + throw new Error('Cloned element does not have an owner document'); + } + this.documentElement = this.cloneNode(element.ownerDocument.documentElement, false); + } + DocumentCloner.prototype.toIFrame = function (ownerDocument, windowSize) { + var _this = this; + var iframe = createIFrameContainer(ownerDocument, windowSize); + if (!iframe.contentWindow) { + return Promise.reject("Unable to find iframe window"); + } + var scrollX = ownerDocument.defaultView.pageXOffset; + var scrollY = ownerDocument.defaultView.pageYOffset; + var cloneWindow = iframe.contentWindow; + var documentClone = cloneWindow.document; + /* Chrome doesn't detect relative background-images assigned in inline '; + + let menuCreated = false; + + function menuOrderListReset(element) { + const defaultOrder = [ + "Scenes", + "Images", + "Movies", + "Markers", + "Galleries", + "Performers", + "Studios", + "Tags", + ]; + const ul = element.querySelector("ul"); + const lis = Array.from(ul.querySelectorAll("li")); + + // Sort the li elements based on their text content + lis.sort((a, b) => { + const aIndex = defaultOrder.indexOf(a.textContent); + const bIndex = defaultOrder.indexOf(b.textContent); + return aIndex - bIndex; + }); + + // Append the sorted li elements to the ul in their new order + lis.forEach((li) => ul.appendChild(li)); + } + + function menuOrderResetButton(elementToAppend) { + if (!document.getElementById("themeSwitchPlugin-menuOrderReset")) { + const resetButton = document.createElement("button"); + resetButton.id = "themeSwitchPlugin-menuOrderReset"; + resetButton.className = "btn btn-primary"; + resetButton.innerHTML = "Reset Menu Order"; + resetButton.addEventListener("click", function () { + localStorage.removeItem( + "themeSwitchPlugin-menu-changeOrderOfNavButtons" + ); + document + .getElementById("themeSwitchPlugin-menu-changeOrderOfNavButtons") + .remove(); + menuOrderListReset(elementToAppend); + resetButton.remove(); + }); + elementToAppend.appendChild(resetButton); + } + } + + function buldDragCSS(themename, key) { + const container = document.querySelector(".draggable-ul-container"); + const list = container.querySelectorAll("li"); + let css = ""; + + for (let i = 0; i < list.length; i++) { + const item = list[i]; + const key = item.getAttribute("data-rb-event-key"); + const order = i - list.length; + css += `div.nav-link[data-rb-event-key="${key}"] { order: ${order}; }`; + } + + const style = document.createElement("style"); + style.type = "text/css"; + style.id = key; + style.innerHTML = `nav .navbar-nav:first-child { + display: flex; + flex-direction: row; + } + ${css}`; + + const existing = document.getElementById(key); + if (existing) { + existing.remove(); + } + + document.getElementsByTagName("head")[0].appendChild(style); + + const data = { + name: themename, + css: css, + key: key, + id: key, + applied: "true", + }; + + localStorage.setItem(key, JSON.stringify(data)); + menuOrderResetButton(container.parentElement); + } + + function enableDragSort(listClass) { + const sortableLists = document.getElementsByClassName(listClass); + Array.prototype.map.call(sortableLists, (list) => { + enableDragList(list); + }); + } + + function enableDragList(list) { + Array.prototype.map.call(list.children, (item) => { + enableDragItem(item); + }); + } + + function enableDragItem(item) { + item.setAttribute("draggable", true); + item.addEventListener("touchstart", handleTouchStart, { passive: true }); + item.addEventListener("touchmove", handleTouchMove, { passive: true }); + item.addEventListener("touchend", handleTouchEnd, { passive: true }); + item.ondrag = handleDrag; + item.ondragend = handleDrop; + } + + let touchEndY = null; + + function handleTouchStart(event) { + event.preventDefault(); + touchStartY = event.touches[0].clientY; + } + + function handleTouchMove(event) { + event.preventDefault(); + touchEndY = event.changedTouches[0].clientY; + const selectedItem = event.target, + list = selectedItem.parentNode; + selectedItem.classList.add("drag-sort-active"); + let swapItem = + document.elementFromPoint( + selectedItem.getBoundingClientRect().x, + touchEndY + ) === null + ? selectedItem + : document.elementFromPoint( + selectedItem.getBoundingClientRect().x, + touchEndY + ); + + if (list === swapItem.parentNode) { + swapItem = + swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling; + list.insertBefore(selectedItem, swapItem); + } + } + + function handleTouchEnd(event) { + event.preventDefault(); + touchEndY = event.changedTouches[0].clientY; + handleDragTouch(event.target); + } + + function handleDragTouch(item) { + const selectedItem = item, + list = selectedItem.parentNode; + selectedItem.classList.remove("drag-sort-active"); + let swapItem = + document.elementFromPoint( + selectedItem.getBoundingClientRect().x, + touchEndY + ) === null + ? selectedItem + : document.elementFromPoint( + selectedItem.getBoundingClientRect().x, + touchEndY + ); + + if (list === swapItem.parentNode) { + swapItem = + swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling; + list.insertBefore(selectedItem, swapItem); + } + setTimeout(() => { + buldDragCSS( + "Change the order of navigation bar buttons", + "themeSwitchPlugin-menu-changeOrderOfNavButtons" + ); + }, 100); + } + + function handleDrag(item) { + const selectedItem = item.target, + list = selectedItem.parentNode, + x = event.clientX, + y = event.clientY; + + selectedItem.classList.add("drag-sort-active"); + let swapItem = + document.elementFromPoint(x, y) === null + ? selectedItem + : document.elementFromPoint(x, y); + + if (list === swapItem.parentNode) { + swapItem = + swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling; + list.insertBefore(selectedItem, swapItem); + } + } + + function handleDrop(item) { + item.target.classList.remove("drag-sort-active"); + setTimeout(() => { + buldDragCSS( + "Change the order of navigation bar buttons", + "themeSwitchPlugin-menu-changeOrderOfNavButtons" + ); + }, 100); + } + + (() => { + enableDragSort("drag-sort-enable"); + })(); + + waitForElementClass("draggable-ul-container", function () { + enableDragSort("draggable-ul-container"); + }); + + function setObject(key, category, active) { + return new Promise((resolve, reject) => { + try { + localStorage.setItem( + key, + JSON.stringify({ category: category, active: active }) + ); + resolve(); + } catch (error) { + reject(error); + } + }); + } + + function checkActive(key) { + let object = JSON.parse(localStorage.getItem(key)); + if (object && object.active === true) { + return true; + } else { + return false; + } + } + + function applyStyleToHead(key, css) { + const styleElement = document.createElement("style"); + styleElement.setAttribute("type", "text/css"); + const cssTextNode = document.createTextNode(css); + styleElement.id = key; + styleElement.appendChild(cssTextNode); + document.getElementsByTagName("head")[0].appendChild(styleElement); + } + + function applyCSS(category, key, css, set) { + if (category === "Themes") { + // Turn Off old Theme + let regex = /(themeSwitchPlugin-theme-.*)/; + + for (let i = 0; i < localStorage.length; i++) { + let storageKey = localStorage.key(i); + let match = storageKey.match(regex); + if ( + match && + storageKey !== key && + JSON.parse(localStorage.getItem(storageKey)).active === true + ) { + setObject(storageKey, category, false); + let element = document.getElementById(storageKey); + if (element) { + element.remove(); + } + } + } + + const theme = JSON.parse(localStorage.getItem(key)); + + if (!theme && key != "themeSwitchPlugin-theme-default") { + setObject(key, category, true).then(() => { + applyStyleToHead(key, css); + }); + } else if (!theme && key === "themeSwitchPlugin-theme-default") { + setObject(key, category, true); + } else if (theme && key === "themeSwitchPlugin-theme-default") { + setObject(key, category, true); + } else if (theme && key !== "themeSwitchPlugin-theme-default") { + setObject(key, category, true).then(() => { + applyStyleToHead(key, css); + }); + } + } else { + // CSS Other than themes + const storageObject = JSON.parse(localStorage.getItem(key)); + if (!storageObject || storageObject.active === false) { + setObject(key, category, true).then(() => { + applyStyleToHead(key, css); + }); + } else if (storageObject.active && !document.getElementById(key)) { + applyStyleToHead(key, css); + } else { + setObject(key, category, false).then(() => { + document.getElementById(key).remove(); + }); + } + } + } + + function expandCollapse(category, name, span, collapse) { + let categorySpanClick = document.getElementById(span); + let categoryCollapseClick = document.getElementById(collapse); + let expanded = + categoryCollapseClick.getAttribute("aria-expanded") === "true"; + + if (expanded) { + categoryCollapseClick.setAttribute("aria-expanded", "false"); + categoryCollapseClick.classList.add("expanding"); + setTimeout(function () { + categoryCollapseClick.classList.remove("show"); + categoryCollapseClick.classList.remove("expanding"); + categorySpanClick.innerHTML = svgChevUP + category; + }, 300); + } else { + categoryCollapseClick.setAttribute("aria-expanded", "true"); + categoryCollapseClick.classList.add("expanding"); + setTimeout(function () { + categoryCollapseClick.classList.add("show"); + categoryCollapseClick.classList.remove("expanding"); + categorySpanClick.innerHTML = svgChevDN + category; + }, 300); + } + } + + function createBTNMenu() { + return new Promise(function (resolve, reject) { + waitForElementClass("top-nav", function () { + if (!document.getElementById("themeSwitchPlugin")) { + const pluginDiv = document.createElement("div"); + pluginDiv.className = "mr-2 dropdown"; + pluginDiv.innerHTML = + '"; + + const themesDiv = document.createElement("div"); + themesDiv.className = "dropdown-menu theme-plugin-menu"; + + document.addEventListener("click", function (event) { + const isClickInside = + pluginDiv.contains(event.target) || + themesDiv.contains(event.target); + const isClickInsideThemesDiv = themesDiv.contains(event.target); + const themeSwitchPlugin = + document.getElementById("themeSwitchPlugin"); + const expanded = + themeSwitchPlugin.getAttribute("aria-expanded") === "true"; + + if (expanded && !isClickInsideThemesDiv) { + themeSwitchPlugin.setAttribute("aria-expanded", "false"); + themesDiv.classList.remove("show"); + themesDiv.style = + "position: absolute; to0px; left: 0px; margin: 0px; opacity: 0; pointer-events: none;"; + } else if (!expanded && isClickInside) { + themeSwitchPlugin.setAttribute("aria-expanded", "true"); + themesDiv.classList.add("show"); + themesDiv.style = + "position: absolute; top: 133%; left: 0px; margin: 0px; opacity: 1; pointer-events: auto; width: max-content; min-width: 20rem; left: -575%;"; + } else if (!isClickInside && !isClickInsideThemesDiv) { + themeSwitchPlugin.setAttribute("aria-expanded", "false"); + themesDiv.classList.remove("show"); + themesDiv.style = + "position: absolute; top: 0px; left: 0px; margin: 0px; opacity: 0; pointer-events: none;"; + } + }); + + const accordion = document.createElement("div"); + accordion.className = "criterion-list accordion"; + accordion.style = "max-width: 20rem !important;"; + const header = document.createElement("div"); + header.innerHTML = "Theme/CSS Switcher"; + header.className = "modal-header"; + accordion.append(header); + + Object.entries(themeSwitchCSS).forEach( + ([category, themesInCategory], i) => { + const categoryDiv = document.createElement("div"); + categoryDiv.className = "card"; + categoryDiv.setAttribute("data-type", category); + categoryDiv.setAttribute( + "id", + "themeSwitchPlugin-card-" + category + ); + categoryDiv.style = "padding: 2px !important;"; + const categoryHeader = document.createElement("div"); + categoryHeader.className = "card-header"; + categoryHeader.style = "padding: 0.375rem 0.625rem !important;"; + const categorySpan = document.createElement("span"); + categorySpan.innerHTML = svgChevUP + category; + categorySpan.setAttribute( + "id", + "themeSwitchPlugin-span-" + category + ); + const collapseDiv = document.createElement("div"); + collapseDiv.className = "collapse"; + collapseDiv.setAttribute("aria-expanded", "false"); + collapseDiv.setAttribute( + "id", + "themeSwitchPlugin-collapse-" + category + ); + categoryDiv.addEventListener( + "click", + (function () { + return function (event) { + if (!event.target.closest(".collapse")) { + expandCollapse( + category, + "themeSwitchPlugin-card-" + category, + "themeSwitchPlugin-span-" + category, + "themeSwitchPlugin-collapse-" + category + ); + } + }; + })(i), + { capture: true } + ); + + const cardBody = document.createElement("div"); + cardBody.className = "card-body"; + const editorDiv = document.createElement("div"); + editorDiv.className = "criterion-editor"; + const blankDiv = document.createElement("div"); + const optionListFilter = document.createElement("div"); + optionListFilter.className = "option-list-filter"; + blankDiv.append(optionListFilter); + editorDiv.append(blankDiv); + cardBody.append(editorDiv); + collapseDiv.append(cardBody); + categoryHeader.append(categorySpan, collapseDiv); + const fieldset = document.createElement("fieldset"); + fieldset.className = "checkbox-switch"; + + // Loop over themes in each category + Object.entries(themesInCategory).forEach(([themeId, theme]) => { + if (category === "Navigation") { + // menuOrder(category, theme.key, optionListFilter); + } else { + const forRow = document.createElement("div"); + forRow.className = "checkbox-switch-form-row"; + const legend = document.createElement("legend"); + legend.innerHTML = theme.displayName; + legend.className = "legend-right"; + const input = document.createElement("input"); + + if (category === "Themes") { + input.type = "radio"; + input.name = "themeGroup"; + const active = checkActive(theme.key); + input.checked = active; + } else { + input.type = "checkbox"; + const active = checkActive(theme.key); + input.checked = active; + } + const themeData = { + category: category, + key: theme.key, + css: theme.styles, + }; + + input.setAttribute("id", category + "-" + theme.key); + input.addEventListener( + "click", + (function (category, key, css) { + return function () { + applyCSS(category, key, css); + }; + })(themeData.category, themeData.key, themeData.css), + false + ); + + const label = document.createElement("label"); + label.setAttribute("for", category + "-" + theme.key); + label.title = "Turn " + theme.displayName + " on/off"; + label.className = "checkbox-right"; + + // Append the legend, input, and label elements to the fieldset element + forRow.appendChild(legend); + forRow.appendChild(input); + forRow.appendChild(label); + fieldset.appendChild(forRow); + optionListFilter.appendChild(fieldset); + categoryDiv.append(categoryHeader); + accordion.append(categoryDiv); + } + }); + + Object.entries(themesInCategory).forEach(([themeId, theme]) => { + if (category === "Navigation") { + const forRow = document.createElement("div"); + forRow.className = "switch-form-row"; + const legend = document.createElement("legend"); + legend.innerHTML = theme.displayName; + legend.className = "legend-left"; + + const draggableStylesheetContent = localStorage.getItem( + "themeSwitchPlugin-menu-changeOrderOfNavButtons" + ); + if (draggableStylesheetContent) { + // Read the stylesheet content and create an array called "elements" + var reset = true; + var navMenuItems = []; + const css = JSON.parse(draggableStylesheetContent).css; + const regex = /data-rb-event-key="\/([^"]+)"/g; + let match; + while ((match = regex.exec(css))) { + const key = match[1]; + if (match[1] === "scenes/markers") { + const name = "Markers"; + navMenuItems.push({ name, key }); + } else { + const name = + match[1].charAt(0).toUpperCase() + match[1].slice(1); + navMenuItems.push({ name, key }); + } + } + navMenuItems.sort((a, b) => a.order - b.order); + } else { + var navMenuItems = [ + { name: "Scenes", key: "scenes" }, + { name: "Images", key: "images" }, + { name: "Movies", key: "movies" }, + { name: "Markers", key: "scenes/markers" }, + { name: "Galleries", key: "galleries" }, + { name: "Performers", key: "performers" }, + { name: "Studios", key: "studios" }, + { name: "Tags", key: "tags" }, + ]; + } + // const label = document.createElement("label"); + legend.setAttribute("for", category + "-" + theme.key); + optionListFilter.appendChild(legend); + const formCheck = document.createElement("ul"); + formCheck.className = "draggable-ul-container"; + for (let i = 0; i < navMenuItems.length; i++) { + const li = document.createElement("li"); + li.className = "draggable-li"; + li.setAttribute( + "data-rb-event-key", + "/" + navMenuItems[i].key + ); + li.setAttribute("draggable", true); + const newSpan = document.createElement("span"); + newSpan.className = "grippy"; + li.appendChild(newSpan); + const textNode = document.createTextNode( + navMenuItems[i].name + ); + li.appendChild(textNode); + formCheck.appendChild(li); + } + + optionListFilter.appendChild(formCheck); + + if (reset) { + menuOrderResetButton(optionListFilter); + } + fieldset.appendChild(forRow); + optionListFilter.appendChild(fieldset); + categoryDiv.append(categoryHeader); + accordion.append(categoryDiv); + } + }); + } + ); + themesDiv.append(accordion); + pluginDiv.append(themesDiv); + + waitForElementClass("navbar-buttons", function () { + const mainDiv = + document.getElementsByClassName("navbar-buttons")[0]; + const secondLastChild = + mainDiv.childNodes[mainDiv.childNodes.length - 4]; + mainDiv.insertBefore(pluginDiv, secondLastChild); + + // Resolving the promise once everything is set up + menuCreated = true; + resolve(); + }); + } else { + // Rejecting the promise if the element already exists + reject(new Error("Element already exists")); + } + }); + }); + } + + function returnCSS(key) { + for (const [, categoryThemes] of Object.entries(themeSwitchCSS)) { + for (const [, theme] of Object.entries(categoryThemes)) { + if (key === theme.key) { + return theme.styles; + } + } + } + } + + function init() { + // Apply active Themes and stylesheets + let selectedTheme; + + const regex = /(themeSwitchPlugin(?!-theme-default).*)/; + const defaultRegex = /(themeSwitchPlugin-theme-default)/; + let defaultFound = false; + let appliedThemeOtherThanDefault = []; + + for (let i = 0; i < localStorage.length; i++) { + let key = localStorage.key(i); + let defaultMatch = key.match(defaultRegex); + if (defaultMatch) { + defaultFound = true; + } + + let match = key.match(regex); + if (match) { + selectedTheme = JSON.parse(localStorage.getItem(key)); + if (selectedTheme.active === true) { + appliedThemeOtherThanDefault.push("True"); + const css = returnCSS(key); + applyCSS(selectedTheme.category, key, css, true); + } + } + } + + if (!defaultFound) { + let setDefaultActive; + if (appliedThemeOtherThanDefault.length > 0) { + setDefaultActive = false; + } else { + setDefaultActive = true; + } + setObject("themeSwitchPlugin-theme-default", "Themes", setDefaultActive); + } + } + + const StashPages = [ + "stash:page:scenes", + "stash:page:scene", + "stash:page:images", + "stash:page:image", + "stash:page:movies", + "stash:page:movie", + "stash:page:markers", + "stash:page:galleries", + "stash:page:performers", + "stash:page:performer", + "stash:page:studios", + "stash:page:studio", + "stash:page:tags", + "stash:page:tag", + "stash:page:settings", + "stash:page:stats", + "stash:page:home", + ]; + + function createMenuAndInit() { + if (!menuCreated) { + createBTNMenu() + .then(function () { + console.log("Theme Plugin: Menu created successfully"); + }) + .catch(function (error) { + console.error("Theme Plugin: Error creating menu:", error.message); + }); + menuCreated = true; + } + init(); + } + + for (var i = 0; i < StashPages.length; i++) { + const page = StashPages[i]; + stash.addEventListener(page, createMenuAndInit); + } + + // Reset menuCreated flag on hard refresh + window.addEventListener("beforeunload", function () { + menuCreated = false; + }); +})(); diff --git a/plugins/themeSwitch/themeSwtichDefault.css b/plugins/themeSwitch/themeSwtichDefault.css new file mode 100644 index 00000000..1544e563 --- /dev/null +++ b/plugins/themeSwitch/themeSwtichDefault.css @@ -0,0 +1,233 @@ +.draggable-ul-container { + border: 1px solid black; + margin: 0; + padding: 0; +} +.draggable-li { + padding: 0 20px; + height: 40px; + line-height: 40px; + border-radius: 3px; + background: #137cbd; /* fallback for old browsers */ + background: -webkit-linear-gradient( + to right, + #137cbd, + #0b4970 + ); /* Chrome 10-25, Safari 5.1-6 */ + background: linear-gradient( + to right, + #137cbd, + #0b4970 + ); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ + border-bottom: 1px solid black; + cursor: move; + color: #fff; + list-style: none; +} + +li.drag-sort-active { + background: transparent; + opacity: 0.5; + border: 1px solid #4ca1af; +} +span.grippy { + content: "...."; + width: 10px; + height: 20px; + display: inline-block; + overflow: hidden; + line-height: 5px; + padding: 3px 4px; + cursor: move; + vertical-align: middle; + margin-top: -0.7em; + margin-right: 0.3em; + font-size: 12px; + font-family: sans-serif; + letter-spacing: 2px; + color: #cccccc; + text-shadow: 1px 0 1px black; +} +span.grippy::after { + content: ".. .. .. .."; +} +.dragging { + opacity: 0.5; +} +li.drag-sort-active { + background: transparent; + color: transparent; + border: 1px solid #4ca1af; +} +span.drag-sort-active { + background: transparent; + color: transparent; +} +.dropdown-menu { + max-height: 500px !important; +} +#plugin_theme-menuOrderReset { + margin-top: 10px; +} +.dropdown-menu { + line-height: 1; +} +.theme-plugin-menu { + z-index: 9999999 !important; +} +/* CHECKBOX */ +.checkbox-switch input[type="checkbox"], +.checkbox-switch input[type="radio"] { + width: 0; + height: 0; + top: -9999px; + right: -9999px; + display: flex; + flex-direction: column; + justify-content: flex-end; +} +.checkbox-switch { + margin: 0; + padding: 0; + border: 0; + display: flex; + flex-direction: collumn; +} +.checkbox-switch:after { + content: ""; + display: table; + clear: both; +} +.checkbox-right { + margin-bottom: unset !important; + display: flex !important; + flex-direction: column; + box-sizing: unset; + justify-content: flex-end; + transform: translate(8px, 14px); + position: unset; +} +.legend-left { + clear: both; /* fix for IE */ + margin: 0; + padding: 0; + font-size: unset; + padding-inline-start: unset; + padding-inline-end: unset; + border-width: unset; + border-style: unset; + border-color: unset; + border-image: unset; + width: unset; + padding: 0; + font-size: unset; + margin-bottom: 10px; + font-weight: unset; + display: flex; + flex-direction: row; + align-items: flex-end; + flex-grow: 1; +} +.legend-right { + clear: both; /* fix for IE */ + margin: 0; + padding: 0; + font-size: unset; + padding-inline-start: unset; + padding-inline-end: unset; + border-width: unset; + border-style: unset; + border-color: unset; + border-image: unset; + width: unset; + padding: 0; + font-size: unset; + margin-bottom: unset; + font-weight: unset; + display: flex; + flex-direction: row-reverse; + align-items: flex-end; + flex-grow: 1; +} +.checkbox-switch input[type="checkbox"] + label, +.checkbox-switch input[type="radio"] + label { + user-select: none; +} +.checkbox-switch input[type="checkbox"] + label:before, +.checkbox-switch input[type="radio"] + label:before { + width: 2.3rem; + height: 1.3rem; + font-family: Arial, sans-serif; + content: "•"; + transition: all 0.2s ease; + text-align: left; + font-size: 2.25rem; + line-height: 1rem; + overflow: hidden; + color: black; + border: 0.1949902505rem solid black; + border-radius: 0.65rem; + margin: auto 0; + float: right; + transform: translate(0px, -15px); + background-color: #808b8d; +} + +.checkbox-switch input[type="checkbox"] + label:after, +.checkbox-switch input[type="radio"] + label:after { + display: none; +} +.checkbox-switch input[type="checkbox"]:checked + label:before, +.checkbox-switch input[type="radio"]:checked + label:before { + border-color: #00ac64; + background: #00ac64; + text-align: right; + color: white; +} +.checkbox-switch input[type="checkbox"] + label:active:before, +.checkbox-switch input[type="checkbox"]:checked + label:active:before, +.checkbox-switch input[type="radio"] + label:active:before, +.checkbox-switch input[type="radio"]:checked + label:active:before { + border-color: #808b8d; + background: #808b8d !important; + color: white; +} +.checkbox-switch fieldset { + margin: 0; + padding: 0; + border: 0; + padding: 1rem; + display: flex; + align-items: center; +} +.checkbox-switch a { + text-decoration: none; + font-weight: 500; + color: inherit; + transition: all 0.2s ease; +} +.checkbox-switch a:hover { + text-decoration: underline; + color: #00ac64; +} +/* Overides */ +.checkbox-switch { + margin-inline-start: unset !important; + margin-inline-end: unset !important; + padding-inline-start: unset !important; + padding-inline-end: unset !important; + padding-block-end: unset !important; + min-inline-size: unset !important; + display: flex; + flex-direction: column; +} +.checkbox-switch-form-row { + align-items: center; + display: flex; + flex-direction: row; + margin-bottom: 10px; + justify-content: right; +} +.top-nav:has(.dropdown-menu.theme-plugin-menu.show) { + overflow: visible !important; +} \ No newline at end of file diff --git a/themes/README.md b/themes/README.md new file mode 100644 index 00000000..ca355a98 --- /dev/null +++ b/themes/README.md @@ -0,0 +1,5 @@ +# Adding Theme plugins as part of Theme Switch plugin + +On developing theme plugins, consider adding your theme to plugins/themeSwitch. While some users may prefer to have individual themes installed requiring to so to the settings > plugin page each time to change the theme, other users may prefer a Theme Switch manager always available on the nav bar. + +There is instructions on how to your theme to the plugin in the folders README.md From 4230fbffabbef0e40ab13184796d6b801d638bdb Mon Sep 17 00:00:00 2001 From: Elkorol Date: Fri, 16 Feb 2024 21:27:55 +0000 Subject: [PATCH 86/91] Removed Uneccesary if clause --- plugins/themeSwitch/themeSwitchMain.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/themeSwitch/themeSwitchMain.js b/plugins/themeSwitch/themeSwitchMain.js index 0fa28f04..de1f8020 100644 --- a/plugins/themeSwitch/themeSwitchMain.js +++ b/plugins/themeSwitch/themeSwitchMain.js @@ -436,10 +436,7 @@ fieldset.className = "checkbox-switch"; // Loop over themes in each category - Object.entries(themesInCategory).forEach(([themeId, theme]) => { - if (category === "Navigation") { - // menuOrder(category, theme.key, optionListFilter); - } else { + Object.entries(themesInCategory).forEach(([theme]) => { const forRow = document.createElement("div"); forRow.className = "checkbox-switch-form-row"; const legend = document.createElement("legend"); @@ -487,7 +484,6 @@ optionListFilter.appendChild(fieldset); categoryDiv.append(categoryHeader); accordion.append(categoryDiv); - } }); Object.entries(themesInCategory).forEach(([themeId, theme]) => { From 519969e495440cfe2ab15a7ad49bd04b6b377dd3 Mon Sep 17 00:00:00 2001 From: Elkorol Date: Fri, 16 Feb 2024 22:11:13 +0000 Subject: [PATCH 87/91] Reverted removing if clause, as it was needed --- plugins/themeSwitch/themeSwitchMain.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/themeSwitch/themeSwitchMain.js b/plugins/themeSwitch/themeSwitchMain.js index de1f8020..9dfcd969 100644 --- a/plugins/themeSwitch/themeSwitchMain.js +++ b/plugins/themeSwitch/themeSwitchMain.js @@ -436,7 +436,9 @@ fieldset.className = "checkbox-switch"; // Loop over themes in each category - Object.entries(themesInCategory).forEach(([theme]) => { + Object.entries(themesInCategory).forEach(([themeId, theme]) => { + if (category === "Navigation") { + } else { const forRow = document.createElement("div"); forRow.className = "checkbox-switch-form-row"; const legend = document.createElement("legend"); @@ -484,6 +486,7 @@ optionListFilter.appendChild(fieldset); categoryDiv.append(categoryHeader); accordion.append(categoryDiv); + } }); Object.entries(themesInCategory).forEach(([themeId, theme]) => { From 5022573a3fb4fe67aa9ad7db1e41f5364f218512 Mon Sep 17 00:00:00 2001 From: Elkorol Date: Fri, 16 Feb 2024 23:27:53 +0000 Subject: [PATCH 88/91] Run Prettier --- plugins/themeSwitch/themeSwitch.yml | 10 +++++----- plugins/themeSwitch/themeSwtichDefault.css | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/themeSwitch/themeSwitch.yml b/plugins/themeSwitch/themeSwitch.yml index 2974c528..ae5548b6 100644 --- a/plugins/themeSwitch/themeSwitch.yml +++ b/plugins/themeSwitch/themeSwitch.yml @@ -1,12 +1,12 @@ name: Theme Switch description: Theme and CSS script manager located in main menu bar top right. -url: +url: version: 2.1 ui: requires: - - stashUserscriptLibrary + - stashUserscriptLibrary javascript: - - themeSwitchMain.js - - themeSwitchCSS.js + - themeSwitchMain.js + - themeSwitchCSS.js css: - - themeSwtichDefault.css \ No newline at end of file + - themeSwtichDefault.css diff --git a/plugins/themeSwitch/themeSwtichDefault.css b/plugins/themeSwitch/themeSwtichDefault.css index 1544e563..01b6689b 100644 --- a/plugins/themeSwitch/themeSwtichDefault.css +++ b/plugins/themeSwitch/themeSwtichDefault.css @@ -230,4 +230,4 @@ span.drag-sort-active { } .top-nav:has(.dropdown-menu.theme-plugin-menu.show) { overflow: visible !important; -} \ No newline at end of file +} From 6904369459230884376b3d9d21357ae5601aba13 Mon Sep 17 00:00:00 2001 From: Elkorol Date: Fri, 16 Feb 2024 23:56:32 +0000 Subject: [PATCH 89/91] Intialisation Fix --- plugins/themeSwitch/themeSwitchMain.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/themeSwitch/themeSwitchMain.js b/plugins/themeSwitch/themeSwitchMain.js index 9dfcd969..eba9827c 100644 --- a/plugins/themeSwitch/themeSwitchMain.js +++ b/plugins/themeSwitch/themeSwitchMain.js @@ -1,4 +1,8 @@ -(function () { +export default async () => { + while (!window.stash) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + const svgChevDN = ''; const svgChevUP = @@ -680,4 +684,4 @@ window.addEventListener("beforeunload", function () { menuCreated = false; }); -})(); +}; From 7d6655fd49520aa5eecbd1db7ae0073bc41732a9 Mon Sep 17 00:00:00 2001 From: Elkorol Date: Sat, 17 Feb 2024 00:11:20 +0000 Subject: [PATCH 90/91] Applied Fix --- plugins/themeSwitch/themeSwitchMain.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/themeSwitch/themeSwitchMain.js b/plugins/themeSwitch/themeSwitchMain.js index eba9827c..a24807ef 100644 --- a/plugins/themeSwitch/themeSwitchMain.js +++ b/plugins/themeSwitch/themeSwitchMain.js @@ -1,6 +1,6 @@ -export default async () => { +(function () { while (!window.stash) { - await new Promise((resolve) => setTimeout(resolve, 100)); + new Promise((resolve) => setTimeout(resolve, 100)); } const svgChevDN = @@ -684,4 +684,4 @@ export default async () => { window.addEventListener("beforeunload", function () { menuCreated = false; }); -}; +})(); From 97e49a2e99a8a71bbb7b7580db198e9db22d362d Mon Sep 17 00:00:00 2001 From: Raghavan Date: Sat, 17 Feb 2024 07:06:36 +0530 Subject: [PATCH 91/91] wait for window.stash --- plugins/themeSwitch/themeSwitchMain.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/themeSwitch/themeSwitchMain.js b/plugins/themeSwitch/themeSwitchMain.js index a24807ef..114c13ea 100644 --- a/plugins/themeSwitch/themeSwitchMain.js +++ b/plugins/themeSwitch/themeSwitchMain.js @@ -1,6 +1,6 @@ -(function () { +(async () => { while (!window.stash) { - new Promise((resolve) => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); } const svgChevDN =