mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-18 20:19:30 -07:00
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
082f5f4961 | ||
|
|
9d91becbd5 | ||
|
|
2e51b7eb89 | ||
|
|
7dd8c2242c | ||
|
|
2a10e4944e | ||
|
|
7893586d24 | ||
|
|
5a51f83654 | ||
|
|
e4d5ac4389 | ||
|
|
ea3d58760b | ||
|
|
4812cdb191 | ||
|
|
b93b8d3ac6 | ||
|
|
a7bc6b2df5 | ||
|
|
1cfbc8ac28 | ||
|
|
c4cea2648e | ||
|
|
26bdddc5ff | ||
|
|
437bbf17e0 | ||
|
|
2a715327f6 | ||
|
|
c111de15b1 | ||
|
|
1b9826f582 | ||
|
|
6a3ce498a9 | ||
|
|
8d48e6347e | ||
|
|
4599a5dfc2 | ||
|
|
7536288baa | ||
|
|
e12c32388b | ||
|
|
4198a5fa90 | ||
|
|
089e9b285a | ||
|
|
60e19fed00 | ||
|
|
626433e67d | ||
|
|
cb5080ce41 | ||
|
|
e2f8317669 | ||
|
|
5d9e32f62b | ||
|
|
b96ecd6042 | ||
|
|
5b7ec62106 | ||
|
|
3ca4a0d803 | ||
|
|
a8d8802d9f | ||
|
|
28782217f1 | ||
|
|
cfcda29524 | ||
|
|
fb498971c7 | ||
|
|
9dbb3cb624 | ||
|
|
129f26dd54 | ||
|
|
d1f9b843c3 | ||
|
|
a7b5ec45ed | ||
|
|
5d82276734 | ||
|
|
b0c4fa8ea7 | ||
|
|
a6f983de5d | ||
|
|
3bc232638a | ||
|
|
67944df12f | ||
|
|
e82fe6cab2 | ||
|
|
819df6b2ed | ||
|
|
dfef87ca55 | ||
|
|
477b6d748a | ||
|
|
31a59c5e9e | ||
|
|
e8cdd12151 | ||
|
|
a8bc17ca12 | ||
|
|
4d04fe1945 | ||
|
|
bafd2aa3b3 | ||
|
|
acf50d10d7 | ||
|
|
30a6a3bd23 | ||
|
|
d808782afd | ||
|
|
5327d7c585 | ||
|
|
d7e7fc68ba | ||
|
|
7b637637a1 | ||
|
|
4a2ef258ba | ||
|
|
b2c1c96f56 | ||
|
|
6d98671856 | ||
|
|
0b7605b3ff | ||
|
|
c9ca9c682a | ||
|
|
417a00b9c4 | ||
|
|
d7a4345f53 | ||
|
|
2e47be5635 | ||
|
|
0e6fc9384f | ||
|
|
81eba485d4 | ||
|
|
b1553a9a8b | ||
|
|
66735f4c65 | ||
|
|
3ff49b1106 | ||
|
|
633f6a40e2 | ||
|
|
1ff94546e0 | ||
|
|
e081e139fc | ||
|
|
875113448b | ||
|
|
53a44776de | ||
|
|
ecdaebb6cd | ||
|
|
1a02360214 | ||
|
|
febbd7d45d | ||
|
|
1acf24a519 | ||
|
|
e2afe91131 | ||
|
|
3e51b96825 | ||
|
|
c84b4cf234 | ||
|
|
997207fa90 | ||
|
|
f7d2c7962c | ||
|
|
0812a5a6b8 | ||
|
|
7fd971eb13 | ||
|
|
881b0b6490 | ||
|
|
50d7b14133 | ||
|
|
872d23ad13 | ||
|
|
eeca83a74b | ||
|
|
3170f5b65a | ||
|
|
bf1c5e21bc | ||
|
|
d08e2c92b8 | ||
|
|
ba07636f8f | ||
|
|
a98c6a4a60 | ||
|
|
c1414bbfc5 | ||
|
|
22b81a8e1d | ||
|
|
8432540bde | ||
|
|
bb056d9c4f | ||
|
|
6bf10c103f | ||
|
|
1b1aea5047 | ||
|
|
5132e36e87 | ||
|
|
964c1802d3 | ||
|
|
0af4e20720 | ||
|
|
1f65b08b56 | ||
|
|
9bfaac25f5 | ||
|
|
794b5c0ea6 | ||
|
|
2fd34c2f7f | ||
|
|
44d7da48e3 | ||
|
|
49e9f904e2 | ||
|
|
1a3fade5fc | ||
|
|
4e4c32384a | ||
|
|
88b518b773 | ||
|
|
4eece4684a | ||
|
|
4f9b5ff311 | ||
|
|
c123fd45be | ||
|
|
09506d2d4e | ||
|
|
08350cfb9f | ||
|
|
cf2f36d232 | ||
|
|
480df04e55 | ||
|
|
ae96a07793 | ||
|
|
17ef30c18b | ||
|
|
0717ce1051 | ||
|
|
9fa1b718b8 | ||
|
|
8c6b38bb49 | ||
|
|
80c784262b | ||
|
|
ae1a08eb82 | ||
|
|
ff1a76eef8 | ||
|
|
d9452ca8d2 | ||
|
|
3fef127666 |
@@ -8,10 +8,11 @@
|
|||||||
"prettier",
|
"prettier",
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:solid/typescript"
|
||||||
],
|
],
|
||||||
"overrides": [],
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": ["prettier", "@typescript-eslint"],
|
"plugins": ["prettier", "@typescript-eslint", "solid"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
"project": "./tsconfig.json",
|
"project": "./tsconfig.json",
|
||||||
@@ -22,13 +23,12 @@
|
|||||||
"arrow-body-style": "off",
|
"arrow-body-style": "off",
|
||||||
"prefer-arrow-callback": "off",
|
"prefer-arrow-callback": "off",
|
||||||
"import/no-cycle": "error",
|
"import/no-cycle": "error",
|
||||||
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
|
||||||
"sort-imports": [
|
"sort-imports": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"ignoreCase": false,
|
"ignoreCase": false,
|
||||||
"ignoreDeclarationSort": true,
|
"ignoreDeclarationSort": true,
|
||||||
"ignoreMemberSort": false,
|
"ignoreMemberSort": true,
|
||||||
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
||||||
"allowSeparatedGroups": true
|
"allowSeparatedGroups": true
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,15 @@
|
|||||||
"import/order": [
|
"import/order": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
|
"groups": [
|
||||||
|
"builtin",
|
||||||
|
"external",
|
||||||
|
"internal",
|
||||||
|
"parent",
|
||||||
|
"sibling",
|
||||||
|
"index",
|
||||||
|
"unknown"
|
||||||
|
],
|
||||||
"newlines-between": "always",
|
"newlines-between": "always",
|
||||||
"alphabetize": {
|
"alphabetize": {
|
||||||
"order": "asc",
|
"order": "asc",
|
||||||
|
|||||||
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Environment (please complete the following information):**
|
|
||||||
|
|
||||||
- OS: [e.g. macOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Hugo Version [e.g. v0.114.0 extended]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
115
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
115
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a bug report
|
||||||
|
labels:
|
||||||
|
- 'T: Bug'
|
||||||
|
- 'S: Untriaged'
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Please finish verify steps which list in the end first before create bug report
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduce
|
||||||
|
attributes:
|
||||||
|
label: Step to reproduce
|
||||||
|
description: |
|
||||||
|
Please write down the reproduction steps here and include the error log. If necessary, please provide screenshots or recordings.
|
||||||
|
placeholder: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
[Screen recording]
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Expected behaviour
|
||||||
|
description: |
|
||||||
|
Describe what should happened here
|
||||||
|
placeholder: |
|
||||||
|
It should be ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: actual
|
||||||
|
attributes:
|
||||||
|
label: Actual Behaviour
|
||||||
|
description: |
|
||||||
|
Describe what actually happened here, screenshots is better
|
||||||
|
placeholder: |
|
||||||
|
Actually it ...
|
||||||
|
[Screenshots]
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: env
|
||||||
|
attributes:
|
||||||
|
label: Logs
|
||||||
|
description: |
|
||||||
|
CLI output or browser log.
|
||||||
|
placeholder: |
|
||||||
|
❯ pnpm run server
|
||||||
|
|
||||||
|
> bridget@v1.0.0 server /Users/spedon/eden/hugo/bridget
|
||||||
|
> run-p vite:server hugo:server
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: more
|
||||||
|
attributes:
|
||||||
|
label: Addition details
|
||||||
|
description: |
|
||||||
|
Describe addition details here
|
||||||
|
placeholder: |
|
||||||
|
Additional details and attachments
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: hugo-version
|
||||||
|
attributes:
|
||||||
|
label: Hugo version
|
||||||
|
description: You can get version output with hugo --version
|
||||||
|
placeholder: v0.114.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: bridget-version
|
||||||
|
attributes:
|
||||||
|
label: Bridget version
|
||||||
|
description: Release version or commit SHA
|
||||||
|
placeholder: v1.0.1 or 633f6a40e202a023471c58c09f05a92ec2130c93
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: system
|
||||||
|
attributes:
|
||||||
|
label: OS version
|
||||||
|
description: OS + version code
|
||||||
|
placeholder: Windows 11, macOS 14
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: check
|
||||||
|
attributes:
|
||||||
|
label: Verify steps
|
||||||
|
description: |
|
||||||
|
Please ensure you have obtained all needed options
|
||||||
|
options:
|
||||||
|
- label: Pull request is welcome. Check this if you want to start a pull request
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- label: I have searched on [Issue Tracker](https://github.com/Sped0n/bridget/issues), No duplicate or related open issue has been found
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- label: Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
|
||||||
|
required: true
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: All other questions
|
||||||
|
url: https://github.com/Sped0n/bridget/discussions
|
||||||
|
about: Turn to discussions
|
||||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
60
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea
|
||||||
|
labels:
|
||||||
|
- 'T: Feature'
|
||||||
|
- 'S: Untriaged'
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Please finish verify steps which list in the end first before suggest an idea
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: request
|
||||||
|
attributes:
|
||||||
|
label: Requirement
|
||||||
|
description: |
|
||||||
|
Ddescribe what you need here.
|
||||||
|
placeholder: |
|
||||||
|
I want ABC feature ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: impl
|
||||||
|
attributes:
|
||||||
|
label: Suggested implements
|
||||||
|
description: |
|
||||||
|
Describe your suggested implements here, It's recommend to add a photo if you are making a UI feature request.
|
||||||
|
placeholder: |
|
||||||
|
I recommend add ABC feature to DEF...
|
||||||
|
Photos (if exists)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: more
|
||||||
|
attributes:
|
||||||
|
label: Addition details
|
||||||
|
description: |
|
||||||
|
Describe addition details here
|
||||||
|
placeholder: |
|
||||||
|
Additional details and attachments
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: check
|
||||||
|
attributes:
|
||||||
|
label: Verify steps
|
||||||
|
description: |
|
||||||
|
Please ensure you have obtained all needed options
|
||||||
|
options:
|
||||||
|
- label: Pull request is welcome. Check this if you want to start a pull request
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- label: I have searched on [Issue Tracker](https://github.com/Sped0n/bridget/issues), No duplicate or related open issue has been found
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- label: Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
|
||||||
|
required: true
|
||||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -8,5 +8,5 @@ updates:
|
|||||||
- package-ecosystem: 'npm' # See documentation for possible values
|
- package-ecosystem: 'npm' # See documentation for possible values
|
||||||
directory: '/' # Location of package manifests
|
directory: '/' # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'daily'
|
interval: 'weekly'
|
||||||
open-pull-requests-limit: 1000
|
open-pull-requests-limit: 1000
|
||||||
|
|||||||
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
any_changed: ${{ steps.changed-files-specific.outputs.any_changed }}
|
any_changed: ${{ steps.changed-files-specific.outputs.any_changed }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -45,16 +45,18 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Setup Hugo
|
- name: Setup Hugo
|
||||||
uses: peaceiris/actions-hugo@v2
|
uses: peaceiris/actions-hugo@v2.6.0
|
||||||
with:
|
with:
|
||||||
hugo-version: '0.114.0'
|
hugo-version: '0.114.0'
|
||||||
extended: true
|
extended: true
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
|
|
||||||
@@ -63,17 +65,17 @@ jobs:
|
|||||||
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: ${{ runner.os }}-pnpm-store-
|
restore-keys: ${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Setup hugo cache
|
- name: Setup hugo cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ./exampleSite/resources
|
path: ./exampleSite/resources
|
||||||
key: ${{ runner.os }}-hugo-${{ hashFiles('./exampleSite') }}
|
key: ${{ runner.os }}-hugo-${{ hashFiles('./exampleSite/resources') }}
|
||||||
restore-keys: ${{ runner.os }}-hugo-
|
restore-keys: ${{ runner.os }}-hugo-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -84,10 +86,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Push artifacts
|
- name: Push artifacts
|
||||||
if: ${{ (github.event_name == 'push' || github.event.pull_request.merged == true) && needs.filter.outputs.any_changed == 'true' }}
|
if: ${{ (github.event_name == 'push' || github.event.pull_request.merged == true) && needs.filter.outputs.any_changed == 'true' }}
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.PAT }}
|
commit_message: 'ci: update bundled artifacts [skip ci]'
|
||||||
title: Update bundled artifacts
|
|
||||||
commit-message: Update bundled artifacts
|
|
||||||
branch: update-artifacts-${{ steps.version.outputs.builddate }}
|
|
||||||
base: main
|
|
||||||
|
|||||||
23
.github/workflows/eslint.yml
vendored
23
.github/workflows/eslint.yml
vendored
@@ -17,10 +17,12 @@ jobs:
|
|||||||
name: Lint
|
name: Lint
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8
|
version: 8
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ jobs:
|
|||||||
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Setup pnpm cache
|
- name: Setup pnpm cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
@@ -38,5 +40,18 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint Check
|
||||||
|
continue-on-error: true
|
||||||
|
id: check
|
||||||
run: pnpm run lint:check
|
run: pnpm run lint:check
|
||||||
|
|
||||||
|
- name: Format manually
|
||||||
|
id: format
|
||||||
|
if: ${{ steps.check.outcome == 'failure' }}
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: Commit
|
||||||
|
if: ${{ steps.format.outcome == 'success' }}
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
|
with:
|
||||||
|
commit_message: 'ci: format code'
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,3 +22,6 @@ $RECYCLE.BIN/
|
|||||||
# Hugo
|
# Hugo
|
||||||
.hugo_build.lock
|
.hugo_build.lock
|
||||||
jsconfig.json
|
jsconfig.json
|
||||||
|
|
||||||
|
# css map
|
||||||
|
*.css.map
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
static
|
static
|
||||||
exmapleSite
|
exmapleSite
|
||||||
*.yaml
|
|
||||||
*.yml
|
|
||||||
single.json
|
single.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at
|
||||||
hi@sped0nwen.com.
|
hi@sped0n.com.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
Bridget is a minimal [Hugo](https://gohugo.io) theme designed for photographers / visual artists.
|
Bridget is a minimal [Hugo](https://gohugo.io) theme for photographers/visual artists, powered by [Solid.js](https://www.solidjs.com).
|
||||||
|
|
||||||
It’s based on the https://github.com/tylermcrobert/bridget-pictures-www.
|
Based on the https://github.com/tylermcrobert/bridget-pictures-www.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## [Demo Site](https://bridget-demo.sped0nwen.com)
|
## [Demo Site](https://bridget-demo.sped0n.com)
|
||||||
|
|
||||||
To see this theme in action, here is a live [demo site](https://bridget-demo.sped0nwen.com) which is rendered with **Bridget** theme.
|
To see this theme in action, here is a live [demo site](https://bridget-demo.sped0n.com) which is rendered with **Bridget** theme.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -18,10 +18,9 @@ Head to this [documentation](https://github.com/Sped0n/bridget/blob/main/doc/get
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Blazingly fast**: 99/100 on mobile and 100/100 on desktop in [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights)
|
- **Blazingly fast**: 100/100 on both desktop and mobile in [Google PageSpeed Insights](https://developers.google.com/speed/pagespeed/insights)
|
||||||
|
- Powered by **[Solid.js](https://www.solidjs.com)**, a declarative, efficient, and flexible JavaScript library for building user interfaces
|
||||||
- JS **dynamic loading** (powered by ES6 syntax)
|
- JS **dynamic loading** (powered by ESM)
|
||||||
- JS **code splitting** by [rollup.js](https://rollupjs.org)
|
|
||||||
- Image **Preloading**/**Lazy loading**
|
- Image **Preloading**/**Lazy loading**
|
||||||
- **Dynamic resolution** based on view mode
|
- **Dynamic resolution** based on view mode
|
||||||
- Multiple **analytics** services supported
|
- Multiple **analytics** services supported
|
||||||
@@ -46,3 +45,4 @@ Bridget supports the following languages:
|
|||||||
- https://github.com/tylermcrobert/bridget-pictures-www
|
- https://github.com/tylermcrobert/bridget-pictures-www
|
||||||
- https://www.youtube.com/watch?v=Jt3A2lNN2aE
|
- https://www.youtube.com/watch?v=Jt3A2lNN2aE
|
||||||
- https://github.com/d4cho/bridget-pictures-clone
|
- https://github.com/d4cho/bridget-pictures-clone
|
||||||
|
- https://www.solidjs.com/tutorial
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.info {
|
article {
|
||||||
padding: var(--space-standard);
|
padding: var(--space-standard);
|
||||||
max-width: 25em;
|
max-width: 25em;
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $tablet), (hover: none) {
|
@media (max-width: $tablet), (hover: none) {
|
||||||
.info {
|
article {
|
||||||
margin-top: var(--nav-height);
|
margin-top: var(--nav-height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20vh;
|
gap: 20vh;
|
||||||
|
|
||||||
padding-top: 50vh;
|
padding-top: calc(var(--window-height) * 0.4);
|
||||||
margin-top: calc(var(--nav-height) * -1);
|
margin-top: calc(var(--nav-height) * -1);
|
||||||
|
|
||||||
img {
|
img {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 50vh;
|
top: calc(var(--window-height) * 0.4);
|
||||||
|
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
height: 20vh;
|
height: 20vh;
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 20vh;
|
margin-bottom: calc(var(--window-height) * 0.35);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
$tablet: map-get($breakpoints, 'tablet') - 1;
|
||||||
|
|
||||||
|
@media (max-width: $tablet), (hover: none) {
|
||||||
.container {
|
.container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -17,3 +20,4 @@
|
|||||||
.disableScroll {
|
.disableScroll {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,18 +16,30 @@
|
|||||||
.galleryInner {
|
.galleryInner {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.swiper-slide {
|
.swiper-slide {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadingText {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slideContainer {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
|
|||||||
@@ -9,3 +9,4 @@
|
|||||||
|
|
||||||
@import '_partial/nav';
|
@import '_partial/nav';
|
||||||
@import '_partial/article';
|
@import '_partial/article';
|
||||||
|
@import '_partial/container';
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { scrollable } from './mobile/scroll'
|
|
||||||
|
|
||||||
export let container: HTMLDivElement
|
|
||||||
|
|
||||||
export function initContainer(): void {
|
|
||||||
container = document.getElementsByClassName('container').item(0) as HTMLDivElement
|
|
||||||
scrollable.addWatcher((o) => {
|
|
||||||
if (o) {
|
|
||||||
container.classList.remove('disableScroll')
|
|
||||||
} else {
|
|
||||||
container.classList.add('disableScroll')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { container } from '../container'
|
|
||||||
|
|
||||||
import { active } from './stage'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
const cursor = document.createElement('div')
|
|
||||||
const cursorInner = document.createElement('div')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
function onMouse(e: MouseEvent): void {
|
|
||||||
const x = e.clientX
|
|
||||||
const y = e.clientY
|
|
||||||
cursor.style.transform = `translate3d(${x}px, ${y}px, 0)`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setCustomCursor(text: string): void {
|
|
||||||
cursorInner.innerText = text
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
export function initCustomCursor(): void {
|
|
||||||
// cursor class name
|
|
||||||
cursor.className = 'cursor'
|
|
||||||
// cursor inner class name
|
|
||||||
cursorInner.className = 'cursorInner'
|
|
||||||
// append cursor inner to cursor
|
|
||||||
cursor.append(cursorInner)
|
|
||||||
// append cursor to main
|
|
||||||
container.append(cursor)
|
|
||||||
// bind mousemove event to window
|
|
||||||
window.addEventListener('mousemove', onMouse, { passive: true })
|
|
||||||
// add active callback
|
|
||||||
active.addWatcher((o) => {
|
|
||||||
if (o) {
|
|
||||||
cursor.classList.add('active')
|
|
||||||
} else {
|
|
||||||
cursor.classList.remove('active')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
52
assets/ts/desktop/customCursor.tsx
Normal file
52
assets/ts/desktop/customCursor.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { createSignal, onCleanup, onMount, type Accessor, type JSX } from 'solid-js'
|
||||||
|
|
||||||
|
export default function CustomCursor(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
active: Accessor<boolean>
|
||||||
|
cursorText: Accessor<string>
|
||||||
|
isOpen: Accessor<boolean>
|
||||||
|
}): JSX.Element {
|
||||||
|
// types
|
||||||
|
interface XY {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// variables
|
||||||
|
let controller: AbortController | undefined
|
||||||
|
|
||||||
|
// states
|
||||||
|
const [xy, setXy] = createSignal<XY>({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
const onMouse: (e: MouseEvent) => void = (e) => {
|
||||||
|
const { clientX, clientY } = e
|
||||||
|
setXy({ x: clientX, y: clientY })
|
||||||
|
}
|
||||||
|
|
||||||
|
// effects
|
||||||
|
onMount(() => {
|
||||||
|
controller = new AbortController()
|
||||||
|
const abortSignal = controller.signal
|
||||||
|
window.addEventListener('mousemove', onMouse, {
|
||||||
|
passive: true,
|
||||||
|
signal: abortSignal
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
controller?.abort()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
class="cursor"
|
||||||
|
classList={{ active: props.active() }}
|
||||||
|
style={{ transform: `translate3d(${xy().x}px, ${xy().y}px, 0)` }}
|
||||||
|
>
|
||||||
|
<div class="cursorInner">{props.cursorText()}</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { type ImageJSON } from '../resources'
|
|
||||||
|
|
||||||
import { initCustomCursor } from './customCursor'
|
|
||||||
import { initStage } from './stage'
|
|
||||||
import { initStageNav } from './stageNav'
|
|
||||||
|
|
||||||
export function initDesktop(ijs: ImageJSON[]): void {
|
|
||||||
initCustomCursor()
|
|
||||||
initStage(ijs)
|
|
||||||
initStageNav()
|
|
||||||
}
|
|
||||||
91
assets/ts/desktop/layout.tsx
Normal file
91
assets/ts/desktop/layout.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// eslint-disable-next-line sort-imports
|
||||||
|
import { Show, createMemo, createSignal, type JSX } from 'solid-js'
|
||||||
|
|
||||||
|
import type { ImageJSON } from '../resources'
|
||||||
|
import type { Vector } from '../utils'
|
||||||
|
|
||||||
|
import CustomCursor from './customCursor'
|
||||||
|
import Nav from './nav'
|
||||||
|
import Stage from './stage'
|
||||||
|
import StageNav from './stageNav'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interfaces and types
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DesktopImage extends HTMLImageElement {
|
||||||
|
dataset: {
|
||||||
|
hiUrl: string
|
||||||
|
hiImgH: string
|
||||||
|
hiImgW: string
|
||||||
|
loUrl: string
|
||||||
|
loImgH: string
|
||||||
|
loImgW: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HistoryItem {
|
||||||
|
i: number
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* components
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Desktop(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
ijs: ImageJSON[]
|
||||||
|
prevText: string
|
||||||
|
closeText: string
|
||||||
|
nextText: string
|
||||||
|
loadingText: string
|
||||||
|
}): JSX.Element {
|
||||||
|
const [cordHist, setCordHist] = createSignal<HistoryItem[]>([])
|
||||||
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
|
const [isOpen, setIsOpen] = createSignal(false)
|
||||||
|
const [isAnimating, setIsAnimating] = createSignal(false)
|
||||||
|
const [hoverText, setHoverText] = createSignal('')
|
||||||
|
const [navVector, setNavVector] = createSignal<Vector>('none')
|
||||||
|
|
||||||
|
const active = createMemo(() => isOpen() && !isAnimating())
|
||||||
|
const cursorText = createMemo(() => (isLoading() ? props.loadingText : hoverText()))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Nav />
|
||||||
|
<Show when={props.ijs.length > 0}>
|
||||||
|
<Stage
|
||||||
|
ijs={props.ijs}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
|
isOpen={isOpen}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
isAnimating={isAnimating}
|
||||||
|
setIsAnimating={setIsAnimating}
|
||||||
|
cordHist={cordHist}
|
||||||
|
setCordHist={setCordHist}
|
||||||
|
navVector={navVector}
|
||||||
|
setNavVector={setNavVector}
|
||||||
|
/>
|
||||||
|
<Show when={isOpen()}>
|
||||||
|
<CustomCursor cursorText={cursorText} active={active} isOpen={isOpen} />
|
||||||
|
<StageNav
|
||||||
|
prevText={props.prevText}
|
||||||
|
closeText={props.closeText}
|
||||||
|
nextText={props.nextText}
|
||||||
|
loadingText={props.loadingText}
|
||||||
|
active={active}
|
||||||
|
isAnimating={isAnimating}
|
||||||
|
setCordHist={setCordHist}
|
||||||
|
isOpen={isOpen}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
setHoverText={setHoverText}
|
||||||
|
navVector={navVector}
|
||||||
|
setNavVector={setNavVector}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
66
assets/ts/desktop/nav.tsx
Normal file
66
assets/ts/desktop/nav.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { createEffect } from 'solid-js'
|
||||||
|
|
||||||
|
import { useState } from '../state'
|
||||||
|
import { expand } from '../utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
// threshold div
|
||||||
|
const thresholdDiv = document.getElementsByClassName('threshold')[0] as HTMLDivElement
|
||||||
|
// threshold nums span
|
||||||
|
const thresholdDispNums = Array.from(
|
||||||
|
thresholdDiv.getElementsByClassName('num')
|
||||||
|
) as HTMLSpanElement[]
|
||||||
|
// threshold buttons
|
||||||
|
const decButton = thresholdDiv
|
||||||
|
.getElementsByClassName('dec')
|
||||||
|
.item(0) as HTMLButtonElement
|
||||||
|
const incButton = thresholdDiv
|
||||||
|
.getElementsByClassName('inc')
|
||||||
|
.item(0) as HTMLButtonElement
|
||||||
|
// index div
|
||||||
|
const indexDiv = document.getElementsByClassName('index').item(0) as HTMLDivElement
|
||||||
|
// index nums span
|
||||||
|
const indexDispNums = Array.from(
|
||||||
|
indexDiv.getElementsByClassName('num')
|
||||||
|
) as HTMLSpanElement[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
function updateThresholdText(thresholdValue: string): void {
|
||||||
|
thresholdDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
||||||
|
e.innerText = thresholdValue[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateIndexText(indexValue: string, indexLength: string): void {
|
||||||
|
indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
||||||
|
if (i < 4) {
|
||||||
|
e.innerText = indexValue[i]
|
||||||
|
} else {
|
||||||
|
e.innerText = indexLength[i - 4]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nav component
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Nav(): null {
|
||||||
|
const [state, { incThreshold, decThreshold }] = useState()
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
updateIndexText(expand(state().index + 1), expand(state().length))
|
||||||
|
updateThresholdText(expand(state().threshold))
|
||||||
|
})
|
||||||
|
|
||||||
|
decButton.onclick = decThreshold
|
||||||
|
incButton.onclick = incThreshold
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
import { type Power3, type gsap } from 'gsap'
|
|
||||||
|
|
||||||
import { container } from '../container'
|
|
||||||
import { type ImageJSON } from '../resources'
|
|
||||||
import { incIndex, state } from '../state'
|
|
||||||
import { Watchable, decrement, increment, loadGsap } from '../utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* types
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface HistoryItem {
|
|
||||||
i: number
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
let imgs: HTMLImageElement[] = []
|
|
||||||
let last = { x: 0, y: 0 }
|
|
||||||
export const cordHist = new Watchable<HistoryItem[]>([])
|
|
||||||
export const isOpen = new Watchable<boolean>(false)
|
|
||||||
export const isAnimating = new Watchable<boolean>(false)
|
|
||||||
export const active = new Watchable<boolean>(false)
|
|
||||||
|
|
||||||
let _gsap: typeof gsap
|
|
||||||
let _Power3: typeof Power3
|
|
||||||
|
|
||||||
let gsapLoaded = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getter
|
|
||||||
*/
|
|
||||||
|
|
||||||
function getElTrail(): HTMLImageElement[] {
|
|
||||||
return cordHist.get().map((item) => imgs[item.i])
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElTrailCurrent(): HTMLImageElement[] {
|
|
||||||
return getElTrail().slice(-state.get().trailLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElTrailInactive(): HTMLImageElement[] {
|
|
||||||
const elTrailCurrent = getElTrailCurrent()
|
|
||||||
return elTrailCurrent.slice(0, elTrailCurrent.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElCurrent(): HTMLImageElement {
|
|
||||||
const elTrail = getElTrail()
|
|
||||||
return elTrail[elTrail.length - 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElNextSeven(): HTMLImageElement[] {
|
|
||||||
const c = cordHist.get()
|
|
||||||
const s = state.get()
|
|
||||||
const c0 = c.length > 0 ? c[c.length - 1].i : s.index
|
|
||||||
const els = []
|
|
||||||
for (let i = 0; i < 7; i++) {
|
|
||||||
els.push(imgs[increment(c0 + i, s.length)])
|
|
||||||
}
|
|
||||||
return els
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElPrev(): HTMLImageElement {
|
|
||||||
const c = cordHist.get()
|
|
||||||
const s = state.get()
|
|
||||||
return imgs[decrement(c[c.length - 1].i, s.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getElNext(): HTMLImageElement {
|
|
||||||
const c = cordHist.get()
|
|
||||||
const s = state.get()
|
|
||||||
return imgs[increment(c[c.length - 1].i, s.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
// on mouse
|
|
||||||
function onMouse(e: MouseEvent): void {
|
|
||||||
if (isOpen.get() || isAnimating.get() || !gsapLoaded) return
|
|
||||||
const cord = { x: e.clientX, y: e.clientY }
|
|
||||||
const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
|
|
||||||
|
|
||||||
if (travelDist > state.get().threshold) {
|
|
||||||
last = cord
|
|
||||||
incIndex()
|
|
||||||
|
|
||||||
const newHist = { i: state.get().index, ...cord }
|
|
||||||
cordHist.set([...cordHist.get(), newHist].slice(-state.get().length))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set image position with gsap
|
|
||||||
function setPositions(): void {
|
|
||||||
const elTrail = getElTrail()
|
|
||||||
if (elTrail.length === 0 || !gsapLoaded) return
|
|
||||||
|
|
||||||
// preload
|
|
||||||
lores(getElNextSeven())
|
|
||||||
|
|
||||||
_gsap.set(elTrail, {
|
|
||||||
x: (i: number) => cordHist.get()[i].x - window.innerWidth / 2,
|
|
||||||
y: (i: number) => cordHist.get()[i].y - window.innerHeight / 2,
|
|
||||||
opacity: (i: number) =>
|
|
||||||
i + 1 + state.get().trailLength <= cordHist.get().length ? 0 : 1,
|
|
||||||
zIndex: (i: number) => i,
|
|
||||||
scale: 0.6
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isOpen.get()) {
|
|
||||||
lores(getElTrail())
|
|
||||||
hires([getElCurrent(), getElPrev(), getElNext()])
|
|
||||||
_gsap.set(imgs, { opacity: 0 })
|
|
||||||
_gsap.set(getElCurrent(), { opacity: 1, x: 0, y: 0, scale: 1 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// open image into navigation
|
|
||||||
function expandImage(): void {
|
|
||||||
if (isAnimating.get() || !gsapLoaded) return
|
|
||||||
|
|
||||||
isOpen.set(true)
|
|
||||||
isAnimating.set(true)
|
|
||||||
|
|
||||||
hires([getElCurrent(), getElPrev(), getElNext()])
|
|
||||||
|
|
||||||
const tl = _gsap.timeline()
|
|
||||||
// move down and hide trail inactive
|
|
||||||
tl.to(getElTrailInactive(), {
|
|
||||||
y: '+=20',
|
|
||||||
ease: _Power3.easeIn,
|
|
||||||
stagger: 0.075,
|
|
||||||
duration: 0.3,
|
|
||||||
delay: 0.1,
|
|
||||||
opacity: 0
|
|
||||||
})
|
|
||||||
// current move to center
|
|
||||||
tl.to(getElCurrent(), {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
ease: _Power3.easeInOut,
|
|
||||||
duration: 0.7,
|
|
||||||
delay: 0.3
|
|
||||||
})
|
|
||||||
// current expand
|
|
||||||
tl.to(getElCurrent(), {
|
|
||||||
delay: 0.1,
|
|
||||||
scale: 1,
|
|
||||||
ease: _Power3.easeInOut
|
|
||||||
})
|
|
||||||
// finished
|
|
||||||
tl.then(() => {
|
|
||||||
isAnimating.set(false)
|
|
||||||
}).catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// close navigation and back to stage
|
|
||||||
export function minimizeImage(): void {
|
|
||||||
if (isAnimating.get() || !gsapLoaded) return
|
|
||||||
|
|
||||||
isOpen.set(false)
|
|
||||||
isAnimating.set(true)
|
|
||||||
|
|
||||||
lores([getElCurrent()])
|
|
||||||
lores(getElTrailInactive())
|
|
||||||
|
|
||||||
const tl = _gsap.timeline()
|
|
||||||
// shrink current
|
|
||||||
tl.to(getElCurrent(), {
|
|
||||||
scale: 0.6,
|
|
||||||
duration: 0.6,
|
|
||||||
ease: _Power3.easeInOut
|
|
||||||
})
|
|
||||||
// move current to original position
|
|
||||||
tl.to(getElCurrent(), {
|
|
||||||
delay: 0.3,
|
|
||||||
duration: 0.7,
|
|
||||||
ease: _Power3.easeInOut,
|
|
||||||
x: cordHist.get()[cordHist.get().length - 1].x - window.innerWidth / 2,
|
|
||||||
y: cordHist.get()[cordHist.get().length - 1].y - window.innerHeight / 2
|
|
||||||
})
|
|
||||||
// show trail inactive
|
|
||||||
tl.to(getElTrailInactive(), {
|
|
||||||
y: '-=20',
|
|
||||||
ease: _Power3.easeOut,
|
|
||||||
stagger: -0.1,
|
|
||||||
duration: 0.3,
|
|
||||||
opacity: 1
|
|
||||||
})
|
|
||||||
// finished
|
|
||||||
tl.then(() => {
|
|
||||||
isAnimating.set(false)
|
|
||||||
}).catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initStage(ijs: ImageJSON[]): void {
|
|
||||||
// create stage element
|
|
||||||
createStage(ijs)
|
|
||||||
// get stage
|
|
||||||
const stage = document.getElementsByClassName('stage').item(0) as HTMLDivElement
|
|
||||||
// get image elements
|
|
||||||
imgs = Array.from(stage.getElementsByTagName('img'))
|
|
||||||
// event listeners
|
|
||||||
stage.addEventListener('click', () => {
|
|
||||||
expandImage()
|
|
||||||
})
|
|
||||||
stage.addEventListener('keydown', () => {
|
|
||||||
expandImage()
|
|
||||||
})
|
|
||||||
window.addEventListener('mousemove', onMouse, { passive: true })
|
|
||||||
// watchers
|
|
||||||
isOpen.addWatcher((o) => {
|
|
||||||
active.set(o && !isAnimating.get())
|
|
||||||
})
|
|
||||||
isAnimating.addWatcher((o) => {
|
|
||||||
active.set(isOpen.get() && !o)
|
|
||||||
})
|
|
||||||
cordHist.addWatcher((_) => {
|
|
||||||
setPositions()
|
|
||||||
})
|
|
||||||
// preload
|
|
||||||
lores(getElNextSeven())
|
|
||||||
// dynamic import
|
|
||||||
window.addEventListener(
|
|
||||||
'mousemove',
|
|
||||||
() => {
|
|
||||||
loadGsap()
|
|
||||||
.then((g) => {
|
|
||||||
_gsap = g[0]
|
|
||||||
_Power3 = g[1]
|
|
||||||
gsapLoaded = true
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ once: true, passive: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hepler
|
|
||||||
*/
|
|
||||||
|
|
||||||
function createStage(ijs: ImageJSON[]): void {
|
|
||||||
// create container for images
|
|
||||||
const stage: HTMLDivElement = document.createElement('div')
|
|
||||||
stage.className = 'stage'
|
|
||||||
// append images to container
|
|
||||||
for (const ij of ijs) {
|
|
||||||
const e = document.createElement('img')
|
|
||||||
e.height = ij.loImgH
|
|
||||||
e.width = ij.loImgW
|
|
||||||
// set data attributes
|
|
||||||
e.dataset.hiUrl = ij.hiUrl
|
|
||||||
e.dataset.hiImgH = ij.hiImgH.toString()
|
|
||||||
e.dataset.hiImgW = ij.hiImgW.toString()
|
|
||||||
e.dataset.loUrl = ij.loUrl
|
|
||||||
e.dataset.loImgH = ij.loImgH.toString()
|
|
||||||
e.dataset.loImgW = ij.loImgW.toString()
|
|
||||||
e.alt = ij.alt
|
|
||||||
stage.append(e)
|
|
||||||
}
|
|
||||||
container.append(stage)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hires(imgs: HTMLImageElement[]): void {
|
|
||||||
imgs.forEach((img) => {
|
|
||||||
img.src = img.dataset.hiUrl as string
|
|
||||||
img.height = parseInt(img.dataset.hiImgH as string)
|
|
||||||
img.width = parseInt(img.dataset.hiImgW as string)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function lores(imgs: HTMLImageElement[]): void {
|
|
||||||
imgs.forEach((img) => {
|
|
||||||
img.src = img.dataset.loUrl as string
|
|
||||||
img.height = parseInt(img.dataset.loImgH as string)
|
|
||||||
img.width = parseInt(img.dataset.loImgW as string)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
476
assets/ts/desktop/stage.tsx
Normal file
476
assets/ts/desktop/stage.tsx
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
import { type gsap } from 'gsap'
|
||||||
|
import {
|
||||||
|
For,
|
||||||
|
createEffect,
|
||||||
|
on,
|
||||||
|
onMount,
|
||||||
|
type Accessor,
|
||||||
|
type JSX,
|
||||||
|
type Setter
|
||||||
|
} from 'solid-js'
|
||||||
|
|
||||||
|
import type { ImageJSON } from '../resources'
|
||||||
|
import { useState, type State } from '../state'
|
||||||
|
import { decrement, increment, loadGsap, type Vector } from '../utils'
|
||||||
|
|
||||||
|
import type { DesktopImage, HistoryItem } from './layout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getTrailElsIndex(cordHistValue: HistoryItem[]): number[] {
|
||||||
|
return cordHistValue.map((el) => el.i)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrailCurrentElsIndex(
|
||||||
|
cordHistValue: HistoryItem[],
|
||||||
|
stateValue: State
|
||||||
|
): number[] {
|
||||||
|
return getTrailElsIndex(cordHistValue).slice(-stateValue.trailLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrailInactiveElsIndex(
|
||||||
|
cordHistValue: HistoryItem[],
|
||||||
|
stateValue: State
|
||||||
|
): number[] {
|
||||||
|
return getTrailCurrentElsIndex(cordHistValue, stateValue).slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentElIndex(cordHistValue: HistoryItem[]): number {
|
||||||
|
return getTrailElsIndex(cordHistValue).slice(-1)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrevElIndex(cordHistValue: HistoryItem[], stateValue: State): number {
|
||||||
|
return decrement(cordHistValue.slice(-1)[0].i, stateValue.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextElIndex(cordHistValue: HistoryItem[], stateValue: State): number {
|
||||||
|
return increment(cordHistValue.slice(-1)[0].i, stateValue.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImagesFromIndexes(imgs: DesktopImage[], indexes: number[]): DesktopImage[] {
|
||||||
|
return indexes.map((i) => imgs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
function hires(imgs: DesktopImage[]): void {
|
||||||
|
imgs.forEach((img) => {
|
||||||
|
if (img.src === img.dataset.hiUrl) return
|
||||||
|
img.src = img.dataset.hiUrl
|
||||||
|
img.height = parseInt(img.dataset.hiImgH)
|
||||||
|
img.width = parseInt(img.dataset.hiImgW)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function lores(imgs: DesktopImage[]): void {
|
||||||
|
imgs.forEach((img) => {
|
||||||
|
if (img.src === img.dataset.loUrl) return
|
||||||
|
img.src = img.dataset.loUrl
|
||||||
|
img.height = parseInt(img.dataset.loImgH)
|
||||||
|
img.width = parseInt(img.dataset.loImgW)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMutation<T extends HTMLElement>(
|
||||||
|
element: T,
|
||||||
|
trigger: (arg0: MutationRecord) => boolean,
|
||||||
|
observeOptions: MutationObserverInit = { attributes: true }
|
||||||
|
): void {
|
||||||
|
new MutationObserver((mutations, observer) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (trigger(mutation)) {
|
||||||
|
observer.disconnect()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).observe(element, observeOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage component
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Stage(props: {
|
||||||
|
ijs: ImageJSON[]
|
||||||
|
setIsLoading: Setter<boolean>
|
||||||
|
isOpen: Accessor<boolean>
|
||||||
|
setIsOpen: Setter<boolean>
|
||||||
|
isAnimating: Accessor<boolean>
|
||||||
|
setIsAnimating: Setter<boolean>
|
||||||
|
cordHist: Accessor<HistoryItem[]>
|
||||||
|
setCordHist: Setter<HistoryItem[]>
|
||||||
|
navVector: Accessor<Vector>
|
||||||
|
setNavVector: Setter<Vector>
|
||||||
|
}): JSX.Element {
|
||||||
|
// variables
|
||||||
|
let _gsap: typeof gsap
|
||||||
|
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
const imgs: DesktopImage[] = Array<DesktopImage>(props.ijs.length)
|
||||||
|
let last = { x: 0, y: 0 }
|
||||||
|
|
||||||
|
let abortController: AbortController | undefined
|
||||||
|
|
||||||
|
// states
|
||||||
|
let gsapLoaded = false
|
||||||
|
|
||||||
|
const [state, { incIndex }] = useState()
|
||||||
|
const stateLength = state().length
|
||||||
|
|
||||||
|
let mounted = false
|
||||||
|
|
||||||
|
const onMouse: (e: MouseEvent) => void = (e) => {
|
||||||
|
if (props.isOpen() || props.isAnimating() || !gsapLoaded || !mounted) return
|
||||||
|
const cord = { x: e.clientX, y: e.clientY }
|
||||||
|
const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y)
|
||||||
|
|
||||||
|
if (travelDist > state().threshold) {
|
||||||
|
last = cord
|
||||||
|
incIndex()
|
||||||
|
|
||||||
|
const _state = state()
|
||||||
|
const newHist = { i: _state.index, ...cord }
|
||||||
|
props.setCordHist((prev) => [...prev, newHist].slice(-stateLength))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick: () => void = () => {
|
||||||
|
!props.isAnimating() && props.setIsOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPosition: () => void = () => {
|
||||||
|
if (!mounted) return
|
||||||
|
if (imgs.length === 0) return
|
||||||
|
const _cordHist = props.cordHist()
|
||||||
|
const trailElsIndex = getTrailElsIndex(_cordHist)
|
||||||
|
if (trailElsIndex.length === 0) return
|
||||||
|
|
||||||
|
const elsTrail = getImagesFromIndexes(imgs, trailElsIndex)
|
||||||
|
|
||||||
|
const _isOpen = props.isOpen()
|
||||||
|
const _state = state()
|
||||||
|
|
||||||
|
_gsap.set(elsTrail, {
|
||||||
|
x: (i: number) => _cordHist[i].x - window.innerWidth / 2,
|
||||||
|
y: (i: number) => _cordHist[i].y - window.innerHeight / 2,
|
||||||
|
opacity: (i: number) =>
|
||||||
|
Math.max(
|
||||||
|
(i + 1 + _state.trailLength <= _cordHist.length ? 0 : 1) - (_isOpen ? 1 : 0),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
zIndex: (i: number) => i,
|
||||||
|
scale: 0.6
|
||||||
|
})
|
||||||
|
|
||||||
|
if (_isOpen) {
|
||||||
|
const elc = getImagesFromIndexes(imgs, [getCurrentElIndex(_cordHist)])[0]
|
||||||
|
const indexArrayToHires: number[] = []
|
||||||
|
const indexArrayToCleanup: number[] = []
|
||||||
|
switch (props.navVector()) {
|
||||||
|
case 'prev':
|
||||||
|
indexArrayToHires.push(getPrevElIndex(_cordHist, _state))
|
||||||
|
indexArrayToCleanup.push(getNextElIndex(_cordHist, _state))
|
||||||
|
break
|
||||||
|
case 'next':
|
||||||
|
indexArrayToHires.push(getNextElIndex(_cordHist, _state))
|
||||||
|
indexArrayToCleanup.push(getPrevElIndex(_cordHist, _state))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
hires(getImagesFromIndexes(imgs, indexArrayToHires)) // preload
|
||||||
|
_gsap.set(getImagesFromIndexes(imgs, indexArrayToCleanup), { opacity: 0 })
|
||||||
|
_gsap.set(elc, { x: 0, y: 0, scale: 1 }) // set current to center
|
||||||
|
setLoaderForHiresImage(elc) // set loader, if loaded set current opacity to 1
|
||||||
|
} else {
|
||||||
|
lores(elsTrail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandImage: () => Promise<
|
||||||
|
gsap.core.Omit<gsap.core.Timeline, 'then'>
|
||||||
|
> = async () => {
|
||||||
|
// isAnimating is prechecked in isOpen effect
|
||||||
|
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
|
||||||
|
|
||||||
|
props.setIsAnimating(true)
|
||||||
|
|
||||||
|
const _cordHist = props.cordHist()
|
||||||
|
const _state = state()
|
||||||
|
|
||||||
|
const elcIndex = getCurrentElIndex(_cordHist)
|
||||||
|
const elc = imgs[elcIndex]
|
||||||
|
|
||||||
|
// don't hide here because we want a better transition
|
||||||
|
hires(
|
||||||
|
getImagesFromIndexes(imgs, [
|
||||||
|
elcIndex,
|
||||||
|
getPrevElIndex(_cordHist, _state),
|
||||||
|
getNextElIndex(_cordHist, _state)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
setLoaderForHiresImage(elc)
|
||||||
|
|
||||||
|
const tl = _gsap.timeline()
|
||||||
|
const trailInactiveEls = getImagesFromIndexes(
|
||||||
|
imgs,
|
||||||
|
getTrailInactiveElsIndex(_cordHist, _state)
|
||||||
|
)
|
||||||
|
// move down and hide trail inactive
|
||||||
|
tl.to(trailInactiveEls, {
|
||||||
|
y: '+=20',
|
||||||
|
ease: 'power3.in',
|
||||||
|
stagger: 0.075,
|
||||||
|
duration: 0.3,
|
||||||
|
delay: 0.1,
|
||||||
|
opacity: 0
|
||||||
|
})
|
||||||
|
// current move to center
|
||||||
|
tl.to(elc, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
ease: 'power3.inOut',
|
||||||
|
duration: 0.7,
|
||||||
|
delay: 0.3
|
||||||
|
})
|
||||||
|
// current expand
|
||||||
|
tl.to(elc, {
|
||||||
|
delay: 0.1,
|
||||||
|
scale: 1,
|
||||||
|
ease: 'power3.inOut'
|
||||||
|
})
|
||||||
|
// finished
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
return await tl.then(() => {
|
||||||
|
props.setIsAnimating(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimizeImage: () => Promise<
|
||||||
|
gsap.core.Omit<gsap.core.Timeline, 'then'>
|
||||||
|
> = async () => {
|
||||||
|
if (!mounted || !gsapLoaded) throw new Error('not mounted or gsap not loaded')
|
||||||
|
|
||||||
|
props.setIsAnimating(true)
|
||||||
|
props.setNavVector('none') // cleanup
|
||||||
|
|
||||||
|
const _cordHist = props.cordHist()
|
||||||
|
const _state = state()
|
||||||
|
|
||||||
|
const elcIndex = getCurrentElIndex(_cordHist)
|
||||||
|
const elsTrailInactiveIndexes = getTrailInactiveElsIndex(_cordHist, _state)
|
||||||
|
|
||||||
|
lores(getImagesFromIndexes(imgs, [...elsTrailInactiveIndexes, elcIndex]))
|
||||||
|
|
||||||
|
const tl = _gsap.timeline()
|
||||||
|
const elc = getImagesFromIndexes(imgs, [elcIndex])[0]
|
||||||
|
const elsTrailInactive = getImagesFromIndexes(imgs, elsTrailInactiveIndexes)
|
||||||
|
// shrink current
|
||||||
|
tl.to(elc, {
|
||||||
|
scale: 0.6,
|
||||||
|
duration: 0.6,
|
||||||
|
ease: 'power3.inOut'
|
||||||
|
})
|
||||||
|
// move current to original position
|
||||||
|
tl.to(elc, {
|
||||||
|
delay: 0.3,
|
||||||
|
duration: 0.7,
|
||||||
|
ease: 'power3.inOut',
|
||||||
|
x: _cordHist.slice(-1)[0].x - window.innerWidth / 2,
|
||||||
|
y: _cordHist.slice(-1)[0].y - window.innerHeight / 2
|
||||||
|
})
|
||||||
|
// show trail inactive
|
||||||
|
tl.to(elsTrailInactive, {
|
||||||
|
y: '-=20',
|
||||||
|
ease: 'power3.out',
|
||||||
|
stagger: -0.1,
|
||||||
|
duration: 0.3,
|
||||||
|
opacity: 1
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
return await tl.then(() => {
|
||||||
|
props.setIsAnimating(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoaderForHiresImage(img: DesktopImage): void {
|
||||||
|
if (!mounted || !gsapLoaded) return
|
||||||
|
if (!img.complete) {
|
||||||
|
props.setIsLoading(true)
|
||||||
|
// abort controller for cleanup
|
||||||
|
const controller = new AbortController()
|
||||||
|
const abortSignal = controller.signal
|
||||||
|
// event listeners
|
||||||
|
img.addEventListener(
|
||||||
|
'load',
|
||||||
|
() => {
|
||||||
|
_gsap
|
||||||
|
.to(img, { opacity: 1, ease: 'power3.out', duration: 0.5 })
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
.then(() => {
|
||||||
|
props.setIsLoading(false)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
controller.abort()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ once: true, passive: true, signal: abortSignal }
|
||||||
|
)
|
||||||
|
img.addEventListener(
|
||||||
|
'error',
|
||||||
|
() => {
|
||||||
|
_gsap
|
||||||
|
.set(img, { opacity: 1 })
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
.then(() => {
|
||||||
|
props.setIsLoading(false)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
controller.abort()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ once: true, passive: true, signal: abortSignal }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
_gsap
|
||||||
|
.set(img, { opacity: 1 })
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
.then(() => {
|
||||||
|
props.setIsLoading(false)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// preload logic
|
||||||
|
imgs.forEach((img, i) => {
|
||||||
|
// preload first 5 images on page load
|
||||||
|
if (i < 5) {
|
||||||
|
img.src = img.dataset.loUrl
|
||||||
|
}
|
||||||
|
// lores preloader for rest of the images
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
onMutation(img, (mutation) => {
|
||||||
|
// if open or animating, hold
|
||||||
|
if (props.isOpen() || props.isAnimating()) return false
|
||||||
|
// if mutation is not about style attribute, hold
|
||||||
|
if (mutation.attributeName !== 'style') return false
|
||||||
|
const opacity = parseFloat(img.style.opacity)
|
||||||
|
// if opacity is not 1, hold
|
||||||
|
if (opacity !== 1) return false
|
||||||
|
// preload the i + 5th image, if it exists
|
||||||
|
if (i + 5 < imgs.length) {
|
||||||
|
imgs[i + 5].src = imgs[i + 5].dataset.loUrl
|
||||||
|
}
|
||||||
|
// triggered
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// load gsap on mousemove
|
||||||
|
window.addEventListener(
|
||||||
|
'mousemove',
|
||||||
|
() => {
|
||||||
|
loadGsap()
|
||||||
|
.then((g) => {
|
||||||
|
_gsap = g
|
||||||
|
gsapLoaded = true
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ passive: true, once: true }
|
||||||
|
)
|
||||||
|
// event listeners
|
||||||
|
abortController = new AbortController()
|
||||||
|
const abortSignal = abortController.signal
|
||||||
|
window.addEventListener('mousemove', onMouse, {
|
||||||
|
passive: true,
|
||||||
|
signal: abortSignal
|
||||||
|
})
|
||||||
|
// mounted
|
||||||
|
mounted = true
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.cordHist(),
|
||||||
|
() => {
|
||||||
|
setPosition()
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => props.isOpen(),
|
||||||
|
async () => {
|
||||||
|
if (props.isAnimating()) return
|
||||||
|
if (props.isOpen()) {
|
||||||
|
// expand image
|
||||||
|
await expandImage()
|
||||||
|
.catch(() => {
|
||||||
|
void 0
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
.then(() => {
|
||||||
|
// abort controller for cleanup
|
||||||
|
abortController?.abort()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// minimize image
|
||||||
|
await minimizeImage()
|
||||||
|
.catch(() => {
|
||||||
|
void 0
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
.then(() => {
|
||||||
|
// event listeners and its abort controller
|
||||||
|
abortController = new AbortController()
|
||||||
|
const abortSignal = abortController.signal
|
||||||
|
window.addEventListener('mousemove', onMouse, {
|
||||||
|
passive: true,
|
||||||
|
signal: abortSignal
|
||||||
|
})
|
||||||
|
// cleanup isLoading
|
||||||
|
props.setIsLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="stage" onClick={onClick} onKeyDown={onClick}>
|
||||||
|
<For each={props.ijs}>
|
||||||
|
{(ij, i) => (
|
||||||
|
<img
|
||||||
|
ref={imgs[i()]}
|
||||||
|
height={ij.loImgH}
|
||||||
|
width={ij.loImgW}
|
||||||
|
data-hi-url={ij.hiUrl}
|
||||||
|
data-hi-img-h={ij.hiImgH}
|
||||||
|
data-hi-img-w={ij.hiImgW}
|
||||||
|
data-lo-url={ij.loUrl}
|
||||||
|
data-lo-img-h={ij.loImgH}
|
||||||
|
data-lo-img-w={ij.loImgW}
|
||||||
|
alt={ij.alt}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { container } from '../container'
|
|
||||||
import { decIndex, incIndex, state } from '../state'
|
|
||||||
import { decrement, increment } from '../utils'
|
|
||||||
|
|
||||||
import { setCustomCursor } from './customCursor'
|
|
||||||
import { active, cordHist, isAnimating, isOpen, minimizeImage } from './stage'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* types
|
|
||||||
*/
|
|
||||||
|
|
||||||
type NavItem = (typeof navItems)[number]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
const mainDiv = document.getElementById('main') as HTMLDivElement
|
|
||||||
const navItems = [
|
|
||||||
mainDiv.getAttribute('prevText') as string,
|
|
||||||
mainDiv.getAttribute('closeText') as string,
|
|
||||||
mainDiv.getAttribute('nextText') as string
|
|
||||||
] as const
|
|
||||||
|
|
||||||
/**
|
|
||||||
* main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
function handleClick(type: NavItem): void {
|
|
||||||
if (type === navItems[0]) {
|
|
||||||
prevImage()
|
|
||||||
} else if (type === navItems[1]) {
|
|
||||||
minimizeImage()
|
|
||||||
} else {
|
|
||||||
nextImage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKey(e: KeyboardEvent): void {
|
|
||||||
if (isOpen.get() || isAnimating.get()) return
|
|
||||||
switch (e.key) {
|
|
||||||
case 'ArrowLeft':
|
|
||||||
prevImage()
|
|
||||||
break
|
|
||||||
case 'Escape':
|
|
||||||
minimizeImage()
|
|
||||||
break
|
|
||||||
case 'ArrowRight':
|
|
||||||
nextImage()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initStageNav(): void {
|
|
||||||
const navOverlay = document.createElement('div')
|
|
||||||
navOverlay.className = 'navOverlay'
|
|
||||||
for (const navItem of navItems) {
|
|
||||||
const overlay = document.createElement('div')
|
|
||||||
overlay.className = 'overlay'
|
|
||||||
overlay.addEventListener(
|
|
||||||
'click',
|
|
||||||
() => {
|
|
||||||
handleClick(navItem)
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
overlay.addEventListener(
|
|
||||||
'keydown',
|
|
||||||
() => {
|
|
||||||
handleClick(navItem)
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
overlay.addEventListener(
|
|
||||||
'mouseover',
|
|
||||||
() => {
|
|
||||||
setCustomCursor(navItem)
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
overlay.addEventListener(
|
|
||||||
'focus',
|
|
||||||
() => {
|
|
||||||
setCustomCursor(navItem)
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
navOverlay.append(overlay)
|
|
||||||
}
|
|
||||||
active.addWatcher(() => {
|
|
||||||
if (active.get()) {
|
|
||||||
navOverlay.classList.add('active')
|
|
||||||
} else {
|
|
||||||
navOverlay.classList.remove('active')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
container.append(navOverlay)
|
|
||||||
window.addEventListener('keydown', handleKey, { passive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* hepler
|
|
||||||
*/
|
|
||||||
|
|
||||||
function nextImage(): void {
|
|
||||||
if (isAnimating.get()) return
|
|
||||||
cordHist.set(
|
|
||||||
cordHist.get().map((item) => {
|
|
||||||
return { ...item, i: increment(item.i, state.get().length) }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
incIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
function prevImage(): void {
|
|
||||||
if (isAnimating.get()) return
|
|
||||||
cordHist.set(
|
|
||||||
cordHist.get().map((item) => {
|
|
||||||
return { ...item, i: decrement(item.i, state.get().length) }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
decIndex()
|
|
||||||
}
|
|
||||||
106
assets/ts/desktop/stageNav.tsx
Normal file
106
assets/ts/desktop/stageNav.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { For, createEffect, type Accessor, type JSX, type Setter } from 'solid-js'
|
||||||
|
|
||||||
|
import { useState } from '../state'
|
||||||
|
import { decrement, increment, type Vector } from '../utils'
|
||||||
|
|
||||||
|
import type { HistoryItem } from './layout'
|
||||||
|
|
||||||
|
export default function StageNav(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
prevText: string
|
||||||
|
closeText: string
|
||||||
|
nextText: string
|
||||||
|
loadingText: string
|
||||||
|
active: Accessor<boolean>
|
||||||
|
isAnimating: Accessor<boolean>
|
||||||
|
setCordHist: Setter<HistoryItem[]>
|
||||||
|
isOpen: Accessor<boolean>
|
||||||
|
setIsOpen: Setter<boolean>
|
||||||
|
setHoverText: Setter<string>
|
||||||
|
navVector: Accessor<Vector>
|
||||||
|
setNavVector: Setter<Vector>
|
||||||
|
}): JSX.Element {
|
||||||
|
// types
|
||||||
|
type NavItem = (typeof navItems)[number]
|
||||||
|
|
||||||
|
// variables
|
||||||
|
let controller: AbortController | undefined
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
const navItems = [props.prevText, props.closeText, props.nextText] as const
|
||||||
|
|
||||||
|
// states
|
||||||
|
const [state, { incIndex, decIndex }] = useState()
|
||||||
|
|
||||||
|
const stateLength = state().length
|
||||||
|
|
||||||
|
const prevImage: () => void = () => {
|
||||||
|
props.setNavVector('prev')
|
||||||
|
props.setCordHist((c) =>
|
||||||
|
c.map((item) => {
|
||||||
|
return { ...item, i: decrement(item.i, stateLength) }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
decIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeImage: () => void = () => {
|
||||||
|
props.setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextImage: () => void = () => {
|
||||||
|
props.setNavVector('next')
|
||||||
|
props.setCordHist((c) =>
|
||||||
|
c.map((item) => {
|
||||||
|
return { ...item, i: increment(item.i, stateLength) }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
incIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick: (item: NavItem) => void = (item) => {
|
||||||
|
if (!props.isOpen() || props.isAnimating()) return
|
||||||
|
if (item === navItems[0]) prevImage()
|
||||||
|
else if (item === navItems[1]) closeImage()
|
||||||
|
else nextImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKey: (e: KeyboardEvent) => void = (e) => {
|
||||||
|
if (!props.isOpen() || props.isAnimating()) return
|
||||||
|
if (e.key === 'ArrowLeft') prevImage()
|
||||||
|
else if (e.key === 'Escape') closeImage()
|
||||||
|
else if (e.key === 'ArrowRight') nextImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (props.isOpen()) {
|
||||||
|
controller = new AbortController()
|
||||||
|
const abortSignal = controller.signal
|
||||||
|
window.addEventListener('keydown', handleKey, {
|
||||||
|
passive: true,
|
||||||
|
signal: abortSignal
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
controller?.abort()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="navOverlay" classList={{ active: props.active() }}>
|
||||||
|
<For each={navItems}>
|
||||||
|
{(item) => (
|
||||||
|
<div
|
||||||
|
class="overlay"
|
||||||
|
onClick={() => {
|
||||||
|
handleClick(item)
|
||||||
|
}}
|
||||||
|
onFocus={() => props.setHoverText(item)}
|
||||||
|
onMouseOver={() => props.setHoverText(item)}
|
||||||
|
tabIndex="-1"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { initContainer } from './container'
|
|
||||||
import { initNav } from './nav'
|
|
||||||
import { initResources } from './resources'
|
|
||||||
import { initState } from './state'
|
|
||||||
import { isMobile } from './utils'
|
|
||||||
|
|
||||||
initContainer()
|
|
||||||
const ijs = await initResources()
|
|
||||||
initState(ijs.length)
|
|
||||||
initNav()
|
|
||||||
|
|
||||||
// NOTE: it seems firefox and chromnium don't like top layer await
|
|
||||||
// so we are using import then instead
|
|
||||||
if (ijs.length > 0) {
|
|
||||||
if (!isMobile()) {
|
|
||||||
import('./desktop/init')
|
|
||||||
.then((d) => {
|
|
||||||
d.initDesktop(ijs)
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
import('./mobile/init')
|
|
||||||
.then((m) => {
|
|
||||||
m.initMobile(ijs)
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
85
assets/ts/main.tsx
Normal file
85
assets/ts/main.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Match,
|
||||||
|
Show,
|
||||||
|
Switch,
|
||||||
|
createEffect,
|
||||||
|
createResource,
|
||||||
|
createSignal,
|
||||||
|
lazy,
|
||||||
|
type JSX
|
||||||
|
} from 'solid-js'
|
||||||
|
import { render } from 'solid-js/web'
|
||||||
|
|
||||||
|
import { getImageJSON } from './resources'
|
||||||
|
import { StateProvider } from './state'
|
||||||
|
|
||||||
|
import '../scss/style.scss'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interfaces
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Container extends HTMLDivElement {
|
||||||
|
dataset: {
|
||||||
|
next: string
|
||||||
|
close: string
|
||||||
|
prev: string
|
||||||
|
loading: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// container
|
||||||
|
const container = document.getElementsByClassName('container')[0] as Container
|
||||||
|
|
||||||
|
// lazy components
|
||||||
|
const Desktop = lazy(async () => await import('./desktop/layout'))
|
||||||
|
const Mobile = lazy(async () => await import('./mobile/layout'))
|
||||||
|
|
||||||
|
function Main(): JSX.Element {
|
||||||
|
// variables
|
||||||
|
const [ijs] = createResource(getImageJSON)
|
||||||
|
const isMobile =
|
||||||
|
window.matchMedia('(hover: none)').matches &&
|
||||||
|
!window.navigator.userAgent.includes('Win')
|
||||||
|
|
||||||
|
// states
|
||||||
|
const [scrollable, setScollable] = createSignal(true)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (scrollable()) {
|
||||||
|
container.classList.remove('disableScroll')
|
||||||
|
} else {
|
||||||
|
container.classList.add('disableScroll')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Show when={ijs.state === 'ready'}>
|
||||||
|
<StateProvider length={ijs()?.length ?? 0}>
|
||||||
|
<Switch fallback={<div>Error</div>}>
|
||||||
|
<Match when={isMobile}>
|
||||||
|
<Mobile
|
||||||
|
ijs={ijs() ?? []}
|
||||||
|
closeText={container.dataset.close}
|
||||||
|
loadingText={container.dataset.loading}
|
||||||
|
setScrollable={setScollable}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
<Match when={!isMobile}>
|
||||||
|
<Desktop
|
||||||
|
ijs={ijs() ?? []}
|
||||||
|
prevText={container.dataset.prev}
|
||||||
|
closeText={container.dataset.close}
|
||||||
|
nextText={container.dataset.next}
|
||||||
|
loadingText={container.dataset.loading}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</StateProvider>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => <Main />, container)
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { container } from '../container'
|
|
||||||
import { type ImageJSON } from '../resources'
|
|
||||||
import { setIndex } from '../state'
|
|
||||||
import { getRandom, onVisible } from '../utils'
|
|
||||||
|
|
||||||
import { slideUp } from './gallery'
|
|
||||||
import { mounted } from './mounted'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
export let imgs: HTMLImageElement[] = []
|
|
||||||
|
|
||||||
/**
|
|
||||||
* main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
function handleClick(i: number): void {
|
|
||||||
setIndex(i)
|
|
||||||
slideUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initCollection(ijs: ImageJSON[]): void {
|
|
||||||
createCollection(ijs)
|
|
||||||
// get container
|
|
||||||
const collection = document
|
|
||||||
.getElementsByClassName('collection')
|
|
||||||
.item(0) as HTMLDivElement
|
|
||||||
// add watcher
|
|
||||||
mounted.addWatcher((o) => {
|
|
||||||
if (o) {
|
|
||||||
collection.classList.remove('hidden')
|
|
||||||
} else {
|
|
||||||
collection.classList.add('hidden')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// get image elements
|
|
||||||
imgs = Array.from(collection.getElementsByTagName('img'))
|
|
||||||
// add event listeners
|
|
||||||
imgs.forEach((img, i) => {
|
|
||||||
img.addEventListener(
|
|
||||||
'click',
|
|
||||||
() => {
|
|
||||||
handleClick(i)
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
img.addEventListener(
|
|
||||||
'keydown',
|
|
||||||
() => {
|
|
||||||
handleClick(i)
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
// preload
|
|
||||||
onVisible(img, () => {
|
|
||||||
for (let _i = 0; _i < 5; _i++) {
|
|
||||||
const n = i + _i
|
|
||||||
if (n < 0 || n > imgs.length - 1) continue
|
|
||||||
imgs[n].src = imgs[n].dataset.src as string
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper
|
|
||||||
*/
|
|
||||||
|
|
||||||
function createCollection(ijs: ImageJSON[]): void {
|
|
||||||
// create container for images
|
|
||||||
const _collection: HTMLDivElement = document.createElement('div')
|
|
||||||
_collection.className = 'collection'
|
|
||||||
// append images to container
|
|
||||||
for (const [i, ij] of ijs.entries()) {
|
|
||||||
// random x and y
|
|
||||||
const x = i !== 0 ? getRandom(-25, 25) : 0
|
|
||||||
const y = i !== 0 ? getRandom(-30, 30) : 0
|
|
||||||
// element
|
|
||||||
const e = document.createElement('img')
|
|
||||||
e.dataset.src = ij.loUrl
|
|
||||||
e.height = ij.loImgH
|
|
||||||
e.width = ij.loImgW
|
|
||||||
e.alt = ij.alt
|
|
||||||
e.style.transform = `translate3d(${x}%, ${y - 50}%, 0)`
|
|
||||||
_collection.append(e)
|
|
||||||
}
|
|
||||||
container.append(_collection)
|
|
||||||
}
|
|
||||||
133
assets/ts/mobile/collection.tsx
Normal file
133
assets/ts/mobile/collection.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import {
|
||||||
|
For,
|
||||||
|
createEffect,
|
||||||
|
on,
|
||||||
|
onMount,
|
||||||
|
type Accessor,
|
||||||
|
type JSX,
|
||||||
|
type Setter
|
||||||
|
} from 'solid-js'
|
||||||
|
|
||||||
|
import type { ImageJSON } from '../resources'
|
||||||
|
import { useState } from '../state'
|
||||||
|
|
||||||
|
import type { MobileImage } from './layout'
|
||||||
|
|
||||||
|
function getRandom(min: number, max: number): number {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
function onIntersection<T extends HTMLElement>(
|
||||||
|
element: T,
|
||||||
|
trigger: (arg0: IntersectionObserverEntry) => boolean
|
||||||
|
): void {
|
||||||
|
new IntersectionObserver((entries, observer) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (trigger(entry)) {
|
||||||
|
observer.disconnect()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).observe(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Collection(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
ijs: ImageJSON[]
|
||||||
|
isAnimating: Accessor<boolean>
|
||||||
|
isOpen: Accessor<boolean>
|
||||||
|
setIsOpen: Setter<boolean>
|
||||||
|
}): JSX.Element {
|
||||||
|
// variables
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
const imgs: MobileImage[] = Array<MobileImage>(props.ijs.length)
|
||||||
|
|
||||||
|
// states
|
||||||
|
const [state, { setIndex }] = useState()
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
const handleClick: (i: number) => void = (i) => {
|
||||||
|
if (props.isAnimating()) return
|
||||||
|
setIndex(i)
|
||||||
|
props.setIsOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToActive: () => void = () => {
|
||||||
|
imgs[state().index].scrollIntoView({ behavior: 'auto', block: 'center' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// effects
|
||||||
|
onMount(() => {
|
||||||
|
imgs.forEach((img, i) => {
|
||||||
|
// preload first 5 images on page load
|
||||||
|
if (i < 5) {
|
||||||
|
img.src = img.dataset.src
|
||||||
|
}
|
||||||
|
// event listeners
|
||||||
|
img.addEventListener(
|
||||||
|
'click',
|
||||||
|
() => {
|
||||||
|
handleClick(i)
|
||||||
|
},
|
||||||
|
{ passive: true }
|
||||||
|
)
|
||||||
|
img.addEventListener(
|
||||||
|
'keydown',
|
||||||
|
() => {
|
||||||
|
handleClick(i)
|
||||||
|
},
|
||||||
|
{ passive: true }
|
||||||
|
)
|
||||||
|
// preload
|
||||||
|
onIntersection(img, (entry) => {
|
||||||
|
// no intersection, hold
|
||||||
|
if (entry.intersectionRatio <= 0) return false
|
||||||
|
// preload the i + 5th image, if it exists
|
||||||
|
if (i + 5 < imgs.length) {
|
||||||
|
imgs[i + 5].src = imgs[i + 5].dataset.src
|
||||||
|
}
|
||||||
|
// triggered
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => {
|
||||||
|
props.isOpen()
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (!props.isOpen()) scrollToActive() // scroll to active when closed
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="collection">
|
||||||
|
<For each={props.ijs}>
|
||||||
|
{(ij, i) => (
|
||||||
|
<img
|
||||||
|
ref={imgs[i()]}
|
||||||
|
height={ij.loImgH}
|
||||||
|
width={ij.loImgW}
|
||||||
|
data-src={ij.loUrl}
|
||||||
|
alt={ij.alt}
|
||||||
|
style={{
|
||||||
|
transform: `translate3d(${i() !== 0 ? getRandom(-25, 25) : 0}%, ${i() !== 0 ? getRandom(-35, 35) : 0}%, 0)`
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
handleClick(i())
|
||||||
|
}}
|
||||||
|
onKeyDown={() => {
|
||||||
|
handleClick(i())
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
import { type Power3, type gsap } from 'gsap'
|
|
||||||
import { type Swiper } from 'swiper'
|
|
||||||
|
|
||||||
import { container } from '../container'
|
|
||||||
import { type ImageJSON } from '../resources'
|
|
||||||
import { setIndex, state } from '../state'
|
|
||||||
import {
|
|
||||||
Watchable,
|
|
||||||
capitalizeFirstLetter,
|
|
||||||
expand,
|
|
||||||
loadGsap,
|
|
||||||
loadSwiper
|
|
||||||
} from '../utils'
|
|
||||||
|
|
||||||
import { mounted } from './mounted'
|
|
||||||
import { scrollable } from './scroll'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
let swiperNode: HTMLDivElement
|
|
||||||
let gallery: HTMLDivElement
|
|
||||||
let curtain: HTMLDivElement
|
|
||||||
let swiper: Swiper
|
|
||||||
const isAnimating = new Watchable<boolean>(false)
|
|
||||||
let lastIndex = -1
|
|
||||||
let indexDispNums: HTMLSpanElement[] = []
|
|
||||||
let galleryImages: HTMLImageElement[] = []
|
|
||||||
let collectionImages: HTMLImageElement[] = []
|
|
||||||
|
|
||||||
let _Swiper: typeof Swiper
|
|
||||||
let _gsap: typeof gsap
|
|
||||||
let _Power3: typeof Power3
|
|
||||||
|
|
||||||
let libLoaded = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function slideUp(): void {
|
|
||||||
if (isAnimating.get() || !libLoaded) return
|
|
||||||
isAnimating.set(true)
|
|
||||||
|
|
||||||
// load active image
|
|
||||||
loadImages()
|
|
||||||
|
|
||||||
_gsap.to(curtain, {
|
|
||||||
opacity: 1,
|
|
||||||
duration: 1
|
|
||||||
})
|
|
||||||
|
|
||||||
_gsap.to(gallery, {
|
|
||||||
y: 0,
|
|
||||||
ease: _Power3.easeInOut,
|
|
||||||
duration: 1,
|
|
||||||
delay: 0.4
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollable.set(false)
|
|
||||||
isAnimating.set(false)
|
|
||||||
}, 1200)
|
|
||||||
}
|
|
||||||
|
|
||||||
function slideDown(): void {
|
|
||||||
scrollable.set(true)
|
|
||||||
scrollToActive()
|
|
||||||
|
|
||||||
_gsap.to(gallery, {
|
|
||||||
y: '100%',
|
|
||||||
ease: _Power3.easeInOut,
|
|
||||||
duration: 1
|
|
||||||
})
|
|
||||||
|
|
||||||
_gsap.to(curtain, {
|
|
||||||
opacity: 0,
|
|
||||||
duration: 1.2,
|
|
||||||
delay: 0.4
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initGallery(ijs: ImageJSON[]): void {
|
|
||||||
// create gallery
|
|
||||||
createGallery(ijs)
|
|
||||||
// get elements
|
|
||||||
indexDispNums = Array.from(
|
|
||||||
document.getElementsByClassName('nav').item(0)?.getElementsByClassName('num') ?? []
|
|
||||||
) as HTMLSpanElement[]
|
|
||||||
swiperNode = document.getElementsByClassName('galleryInner').item(0) as HTMLDivElement
|
|
||||||
gallery = document.getElementsByClassName('gallery').item(0) as HTMLDivElement
|
|
||||||
curtain = document.getElementsByClassName('curtain').item(0) as HTMLDivElement
|
|
||||||
galleryImages = Array.from(gallery.getElementsByTagName('img'))
|
|
||||||
collectionImages = Array.from(
|
|
||||||
document
|
|
||||||
.getElementsByClassName('collection')
|
|
||||||
.item(0)
|
|
||||||
?.getElementsByTagName('img') ?? []
|
|
||||||
)
|
|
||||||
// state watcher
|
|
||||||
state.addWatcher(() => {
|
|
||||||
const s = state.get()
|
|
||||||
// change slide only when index is changed
|
|
||||||
if (s.index === lastIndex) return
|
|
||||||
changeSlide(s.index)
|
|
||||||
updateIndexText()
|
|
||||||
lastIndex = s.index
|
|
||||||
})
|
|
||||||
// mounted watcher
|
|
||||||
mounted.addWatcher((o) => {
|
|
||||||
if (!o) return
|
|
||||||
scrollable.set(true)
|
|
||||||
})
|
|
||||||
// dynamic import
|
|
||||||
window.addEventListener(
|
|
||||||
'touchstart',
|
|
||||||
() => {
|
|
||||||
loadGsap()
|
|
||||||
.then((g) => {
|
|
||||||
_gsap = g[0]
|
|
||||||
_Power3 = g[1]
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
loadSwiper()
|
|
||||||
.then((s) => {
|
|
||||||
_Swiper = s
|
|
||||||
swiper = new _Swiper(swiperNode, { spaceBetween: 20 })
|
|
||||||
swiper.on('slideChange', ({ realIndex }) => {
|
|
||||||
setIndex(realIndex)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
libLoaded = true
|
|
||||||
},
|
|
||||||
{ once: true, passive: true }
|
|
||||||
)
|
|
||||||
// mounted
|
|
||||||
mounted.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper
|
|
||||||
*/
|
|
||||||
|
|
||||||
function changeSlide(slide: number): void {
|
|
||||||
loadImages()
|
|
||||||
swiper.slideTo(slide, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToActive(): void {
|
|
||||||
collectionImages[state.get().index].scrollIntoView({
|
|
||||||
block: 'center',
|
|
||||||
behavior: 'auto'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateIndexText(): void {
|
|
||||||
const indexValue: string = expand(state.get().index + 1)
|
|
||||||
const indexLength: string = expand(state.get().length)
|
|
||||||
indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
|
||||||
if (i < 4) {
|
|
||||||
e.innerText = indexValue[i]
|
|
||||||
} else {
|
|
||||||
e.innerText = indexLength[i - 4]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGallery(ijs: ImageJSON[]): void {
|
|
||||||
/**
|
|
||||||
* gallery
|
|
||||||
* |- galleryInner
|
|
||||||
* |- swiper-wrapper
|
|
||||||
* |- swiper-slide
|
|
||||||
* |- img
|
|
||||||
* |- swiper-slide
|
|
||||||
* |- img
|
|
||||||
* |- ...
|
|
||||||
* |- nav
|
|
||||||
* |- index
|
|
||||||
* |- close
|
|
||||||
*/
|
|
||||||
// swiper wrapper
|
|
||||||
const _swiperWrapper = document.createElement('div')
|
|
||||||
_swiperWrapper.className = 'swiper-wrapper'
|
|
||||||
// swiper slide
|
|
||||||
for (const ij of ijs) {
|
|
||||||
const _swiperSlide = document.createElement('div')
|
|
||||||
_swiperSlide.className = 'swiper-slide'
|
|
||||||
// img
|
|
||||||
const e = document.createElement('img')
|
|
||||||
e.dataset.src = ij.hiUrl
|
|
||||||
e.height = ij.hiImgH
|
|
||||||
e.width = ij.hiImgW
|
|
||||||
e.alt = ij.alt
|
|
||||||
// append
|
|
||||||
_swiperSlide.append(e)
|
|
||||||
_swiperWrapper.append(_swiperSlide)
|
|
||||||
}
|
|
||||||
// swiper node
|
|
||||||
const _swiperNode = document.createElement('div')
|
|
||||||
_swiperNode.className = 'galleryInner'
|
|
||||||
_swiperNode.append(_swiperWrapper)
|
|
||||||
// index
|
|
||||||
const _index = document.createElement('div')
|
|
||||||
_index.insertAdjacentHTML(
|
|
||||||
'afterbegin',
|
|
||||||
`<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>
|
|
||||||
<span>/</span>
|
|
||||||
<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>`
|
|
||||||
)
|
|
||||||
// close
|
|
||||||
const _close = document.createElement('div')
|
|
||||||
const str: string = document
|
|
||||||
.getElementById('main')
|
|
||||||
?.getAttribute('closeText') as string
|
|
||||||
_close.innerText = capitalizeFirstLetter(str)
|
|
||||||
_close.addEventListener(
|
|
||||||
'click',
|
|
||||||
() => {
|
|
||||||
slideDown()
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
_close.addEventListener(
|
|
||||||
'keydown',
|
|
||||||
() => {
|
|
||||||
slideDown()
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
// nav
|
|
||||||
const _navDiv = document.createElement('div')
|
|
||||||
_navDiv.className = 'nav'
|
|
||||||
_navDiv.append(_index, _close)
|
|
||||||
// gallery
|
|
||||||
const _gallery = document.createElement('div')
|
|
||||||
_gallery.className = 'gallery'
|
|
||||||
_gallery.append(_swiperNode)
|
|
||||||
_gallery.append(_navDiv)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* curtain
|
|
||||||
*/
|
|
||||||
const _curtain = document.createElement('div')
|
|
||||||
_curtain.className = 'curtain'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* container
|
|
||||||
* |- gallery
|
|
||||||
* |- curtain
|
|
||||||
*/
|
|
||||||
container.append(_gallery, _curtain)
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImages(): void {
|
|
||||||
const activeImages: HTMLImageElement[] = []
|
|
||||||
// load current, next, prev image
|
|
||||||
activeImages.push(galleryImages[swiper.activeIndex])
|
|
||||||
activeImages.push(
|
|
||||||
galleryImages[Math.min(swiper.activeIndex + 1, swiper.slides.length)]
|
|
||||||
)
|
|
||||||
activeImages.push(galleryImages[Math.max(swiper.activeIndex - 1, 0)])
|
|
||||||
for (const e of activeImages) {
|
|
||||||
e.src = e.dataset.src as string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
242
assets/ts/mobile/gallery.tsx
Normal file
242
assets/ts/mobile/gallery.tsx
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
import { type gsap } from 'gsap'
|
||||||
|
import {
|
||||||
|
createEffect,
|
||||||
|
createSignal,
|
||||||
|
For,
|
||||||
|
on,
|
||||||
|
onMount,
|
||||||
|
Show,
|
||||||
|
type Accessor,
|
||||||
|
type JSX,
|
||||||
|
type Setter
|
||||||
|
} from 'solid-js'
|
||||||
|
import { createStore } from 'solid-js/store'
|
||||||
|
import { type Swiper } from 'swiper'
|
||||||
|
import invariant from 'tiny-invariant'
|
||||||
|
|
||||||
|
import { type ImageJSON } from '../resources'
|
||||||
|
import { useState } from '../state'
|
||||||
|
import { loadGsap, type Vector } from '../utils'
|
||||||
|
|
||||||
|
import GalleryImage from './galleryImage'
|
||||||
|
import GalleryNav, { capitalizeFirstLetter } from './galleryNav'
|
||||||
|
|
||||||
|
function removeDuplicates<T>(arr: T[]): T[] {
|
||||||
|
if (arr.length < 2) return arr // optimization
|
||||||
|
return [...new Set(arr)]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSwiper(): Promise<typeof Swiper> {
|
||||||
|
const s = await import('swiper')
|
||||||
|
return s.Swiper
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Gallery(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
ijs: ImageJSON[]
|
||||||
|
closeText: string
|
||||||
|
loadingText: string
|
||||||
|
isAnimating: Accessor<boolean>
|
||||||
|
setIsAnimating: Setter<boolean>
|
||||||
|
isOpen: Accessor<boolean>
|
||||||
|
setIsOpen: Setter<boolean>
|
||||||
|
setScrollable: Setter<boolean>
|
||||||
|
}): JSX.Element {
|
||||||
|
// variables
|
||||||
|
let _gsap: typeof gsap
|
||||||
|
let _swiper: Swiper
|
||||||
|
|
||||||
|
let curtain: HTMLDivElement | undefined
|
||||||
|
let gallery: HTMLDivElement | undefined
|
||||||
|
let galleryInner: HTMLDivElement | undefined
|
||||||
|
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
const _loadingText = capitalizeFirstLetter(props.loadingText)
|
||||||
|
|
||||||
|
// states
|
||||||
|
let lastIndex = -1
|
||||||
|
let mounted = false
|
||||||
|
let navigateVector: Vector = 'none'
|
||||||
|
|
||||||
|
const [state, { setIndex }] = useState()
|
||||||
|
const [libLoaded, setLibLoaded] = createSignal(false)
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
const [loads, setLoads] = createStore(Array<boolean>(props.ijs.length).fill(false))
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
const slideUp: () => void = () => {
|
||||||
|
// isAnimating is prechecked in isOpen effect
|
||||||
|
if (!libLoaded() || !mounted) return
|
||||||
|
props.setIsAnimating(true)
|
||||||
|
|
||||||
|
invariant(curtain, 'curtain is not defined')
|
||||||
|
invariant(gallery, 'gallery is not defined')
|
||||||
|
|
||||||
|
_gsap.to(curtain, {
|
||||||
|
opacity: 1,
|
||||||
|
duration: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
_gsap.to(gallery, {
|
||||||
|
y: 0,
|
||||||
|
ease: 'power3.inOut',
|
||||||
|
duration: 1,
|
||||||
|
delay: 0.4
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
props.setScrollable(false)
|
||||||
|
props.setIsAnimating(false)
|
||||||
|
}, 1200)
|
||||||
|
}
|
||||||
|
|
||||||
|
const slideDown: () => void = () => {
|
||||||
|
// isAnimating is prechecked in isOpen effect
|
||||||
|
props.setIsAnimating(true)
|
||||||
|
|
||||||
|
invariant(gallery, 'curtain is not defined')
|
||||||
|
invariant(curtain, 'gallery is not defined')
|
||||||
|
|
||||||
|
_gsap.to(gallery, {
|
||||||
|
y: '100%',
|
||||||
|
ease: 'power3.inOut',
|
||||||
|
duration: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
_gsap.to(curtain, {
|
||||||
|
opacity: 0,
|
||||||
|
duration: 1.2,
|
||||||
|
delay: 0.4
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// cleanup
|
||||||
|
props.setScrollable(true)
|
||||||
|
props.setIsAnimating(false)
|
||||||
|
lastIndex = -1
|
||||||
|
}, 1400)
|
||||||
|
}
|
||||||
|
|
||||||
|
const galleryLoadImages: () => void = () => {
|
||||||
|
let activeImagesIndex: number[] = []
|
||||||
|
const _state = state()
|
||||||
|
const currentIndex = _state.index
|
||||||
|
const nextIndex = Math.min(currentIndex + 1, _state.length - 1)
|
||||||
|
const prevIndex = Math.max(currentIndex - 1, 0)
|
||||||
|
switch (navigateVector) {
|
||||||
|
case 'next':
|
||||||
|
activeImagesIndex = [nextIndex]
|
||||||
|
break
|
||||||
|
case 'prev':
|
||||||
|
activeImagesIndex = [prevIndex]
|
||||||
|
break
|
||||||
|
case 'none':
|
||||||
|
activeImagesIndex = [currentIndex, nextIndex, prevIndex]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
setLoads(removeDuplicates(activeImagesIndex), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeSlide: (slide: number) => void = (slide) => {
|
||||||
|
// we are already in the gallery, don't need to
|
||||||
|
// check mounted or libLoaded
|
||||||
|
galleryLoadImages()
|
||||||
|
_swiper.slideTo(slide, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// effects
|
||||||
|
onMount(() => {
|
||||||
|
window.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
() => {
|
||||||
|
loadGsap()
|
||||||
|
.then((g) => {
|
||||||
|
_gsap = g
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
loadSwiper()
|
||||||
|
.then((S) => {
|
||||||
|
invariant(galleryInner, 'galleryInner is not defined')
|
||||||
|
_swiper = new S(galleryInner, { spaceBetween: 20 })
|
||||||
|
_swiper.on('slideChange', ({ realIndex }) => {
|
||||||
|
setIndex(realIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
setLibLoaded(true)
|
||||||
|
},
|
||||||
|
{ once: true, passive: true }
|
||||||
|
)
|
||||||
|
mounted = true
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => {
|
||||||
|
state()
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const i = state().index
|
||||||
|
if (i === lastIndex)
|
||||||
|
return // change slide only when index is changed
|
||||||
|
else if (lastIndex === -1)
|
||||||
|
navigateVector = 'none' // lastIndex before set
|
||||||
|
else if (i < lastIndex)
|
||||||
|
navigateVector = 'prev' // set navigate vector for galleryLoadImages
|
||||||
|
else if (i > lastIndex)
|
||||||
|
navigateVector = 'next' // set navigate vector for galleryLoadImages
|
||||||
|
else navigateVector = 'none' // default
|
||||||
|
changeSlide(i) // change slide to new index
|
||||||
|
lastIndex = i // update last index
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => {
|
||||||
|
props.isOpen()
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (props.isAnimating()) return
|
||||||
|
if (props.isOpen()) slideUp()
|
||||||
|
else slideDown()
|
||||||
|
},
|
||||||
|
{ defer: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div ref={gallery} class="gallery">
|
||||||
|
<div ref={galleryInner} class="galleryInner">
|
||||||
|
<div class="swiper-wrapper">
|
||||||
|
<Show when={libLoaded()}>
|
||||||
|
<For each={props.ijs}>
|
||||||
|
{(ij, i) => (
|
||||||
|
<div class="swiper-slide">
|
||||||
|
<GalleryImage
|
||||||
|
load={loads[i()]}
|
||||||
|
ij={ij}
|
||||||
|
loadingText={_loadingText}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<GalleryNav
|
||||||
|
closeText={props.closeText}
|
||||||
|
isAnimating={props.isAnimating}
|
||||||
|
setIsOpen={props.setIsOpen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div ref={curtain} class="curtain" />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
69
assets/ts/mobile/galleryImage.tsx
Normal file
69
assets/ts/mobile/galleryImage.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { onMount, type JSX } from 'solid-js'
|
||||||
|
import invariant from 'tiny-invariant'
|
||||||
|
|
||||||
|
import type { ImageJSON } from '../resources'
|
||||||
|
import { useState } from '../state'
|
||||||
|
import { loadGsap } from '../utils'
|
||||||
|
|
||||||
|
export default function GalleryImage(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
load: boolean
|
||||||
|
ij: ImageJSON
|
||||||
|
loadingText: string
|
||||||
|
}): JSX.Element {
|
||||||
|
let img: HTMLImageElement | undefined
|
||||||
|
let loadingDiv: HTMLDivElement | undefined
|
||||||
|
|
||||||
|
let _gsap: typeof gsap
|
||||||
|
|
||||||
|
const [state] = useState()
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
loadGsap()
|
||||||
|
.then((g) => {
|
||||||
|
_gsap = g
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
img?.addEventListener(
|
||||||
|
'load',
|
||||||
|
() => {
|
||||||
|
invariant(img, 'ref must be defined')
|
||||||
|
invariant(loadingDiv, 'loadingDiv must be defined')
|
||||||
|
if (state().index !== props.ij.index) {
|
||||||
|
_gsap.set(img, { opacity: 1 })
|
||||||
|
_gsap.set(loadingDiv, { opacity: 0 })
|
||||||
|
} else {
|
||||||
|
_gsap.to(img, {
|
||||||
|
opacity: 1,
|
||||||
|
delay: 0.5,
|
||||||
|
duration: 0.5,
|
||||||
|
ease: 'power3.out'
|
||||||
|
})
|
||||||
|
_gsap.to(loadingDiv, { opacity: 0, duration: 0.5, ease: 'power3.in' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ once: true, passive: true }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="slideContainer">
|
||||||
|
<img
|
||||||
|
ref={img}
|
||||||
|
{...(props.load && { src: props.ij.hiUrl })}
|
||||||
|
height={props.ij.hiImgH}
|
||||||
|
width={props.ij.hiImgW}
|
||||||
|
data-src={props.ij.hiUrl}
|
||||||
|
alt={props.ij.alt}
|
||||||
|
style={{ opacity: 0 }}
|
||||||
|
/>
|
||||||
|
<div ref={loadingDiv} class="loadingText">
|
||||||
|
{props.loadingText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
46
assets/ts/mobile/galleryNav.tsx
Normal file
46
assets/ts/mobile/galleryNav.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { createMemo, type Accessor, type JSX, type Setter } from 'solid-js'
|
||||||
|
|
||||||
|
import { useState } from '../state'
|
||||||
|
import { expand } from '../utils'
|
||||||
|
|
||||||
|
export function capitalizeFirstLetter(str: string): string {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GalleryNav(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
closeText: string
|
||||||
|
isAnimating: Accessor<boolean>
|
||||||
|
setIsOpen: Setter<boolean>
|
||||||
|
}): JSX.Element {
|
||||||
|
// states
|
||||||
|
const [state] = useState()
|
||||||
|
const indexValue = createMemo(() => expand(state().index + 1))
|
||||||
|
const indexLength = createMemo(() => expand(state().length))
|
||||||
|
|
||||||
|
const onClick: () => void = () => {
|
||||||
|
if (props.isAnimating()) return
|
||||||
|
props.setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="nav">
|
||||||
|
<div>
|
||||||
|
<span class="num">{indexValue()[0]}</span>
|
||||||
|
<span class="num">{indexValue()[1]}</span>
|
||||||
|
<span class="num">{indexValue()[2]}</span>
|
||||||
|
<span class="num">{indexValue()[3]}</span>
|
||||||
|
<span>/</span>
|
||||||
|
<span class="num">{indexLength()[0]}</span>
|
||||||
|
<span class="num">{indexLength()[1]}</span>
|
||||||
|
<span class="num">{indexLength()[2]}</span>
|
||||||
|
<span class="num">{indexLength()[3]}</span>
|
||||||
|
</div>
|
||||||
|
<div onClick={onClick} onKeyDown={onClick}>
|
||||||
|
{capitalizeFirstLetter(props.closeText)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { type ImageJSON } from '../resources'
|
|
||||||
|
|
||||||
import { initCollection } from './collection'
|
|
||||||
import { initGallery } from './gallery'
|
|
||||||
|
|
||||||
export function initMobile(ijs: ImageJSON[]): void {
|
|
||||||
initCollection(ijs)
|
|
||||||
initGallery(ijs)
|
|
||||||
}
|
|
||||||
52
assets/ts/mobile/layout.tsx
Normal file
52
assets/ts/mobile/layout.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Show, createSignal, type JSX, type Setter } from 'solid-js'
|
||||||
|
|
||||||
|
import type { ImageJSON } from '../resources'
|
||||||
|
|
||||||
|
import Collection from './collection'
|
||||||
|
import Gallery from './gallery'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interfaces
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface MobileImage extends HTMLImageElement {
|
||||||
|
dataset: {
|
||||||
|
src: string
|
||||||
|
index: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Mobile(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
ijs: ImageJSON[]
|
||||||
|
closeText: string
|
||||||
|
loadingText: string
|
||||||
|
setScrollable: Setter<boolean>
|
||||||
|
}): JSX.Element {
|
||||||
|
// states
|
||||||
|
const [isOpen, setIsOpen] = createSignal(false)
|
||||||
|
const [isAnimating, setIsAnimating] = createSignal(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Show when={props.ijs.length > 0}>
|
||||||
|
<Collection
|
||||||
|
ijs={props.ijs}
|
||||||
|
isAnimating={isAnimating}
|
||||||
|
isOpen={isOpen}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
/>
|
||||||
|
<Gallery
|
||||||
|
ijs={props.ijs}
|
||||||
|
closeText={props.closeText}
|
||||||
|
loadingText={props.loadingText}
|
||||||
|
isAnimating={isAnimating}
|
||||||
|
setIsAnimating={setIsAnimating}
|
||||||
|
isOpen={isOpen}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
setScrollable={props.setScrollable}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { Watchable } from '../utils'
|
|
||||||
|
|
||||||
export const mounted = new Watchable<boolean>(false)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { Watchable } from '../utils'
|
|
||||||
|
|
||||||
export const scrollable = new Watchable<boolean>(true)
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { decThreshold, incThreshold, state } from './state'
|
|
||||||
import { expand } from './utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
// threshold div
|
|
||||||
const thresholdDiv = document
|
|
||||||
.getElementsByClassName('threshold')
|
|
||||||
.item(0) as HTMLDivElement
|
|
||||||
|
|
||||||
// threshold nums span
|
|
||||||
const thresholdDispNums = Array.from(
|
|
||||||
thresholdDiv.getElementsByClassName('num')
|
|
||||||
) as HTMLSpanElement[]
|
|
||||||
|
|
||||||
// threshold buttons
|
|
||||||
const decButton = thresholdDiv
|
|
||||||
.getElementsByClassName('dec')
|
|
||||||
.item(0) as HTMLButtonElement
|
|
||||||
const incButton = thresholdDiv
|
|
||||||
.getElementsByClassName('inc')
|
|
||||||
.item(0) as HTMLButtonElement
|
|
||||||
|
|
||||||
// index div
|
|
||||||
const indexDiv = document.getElementsByClassName('index').item(0) as HTMLDivElement
|
|
||||||
|
|
||||||
// index nums span
|
|
||||||
const indexDispNums = Array.from(
|
|
||||||
indexDiv.getElementsByClassName('num')
|
|
||||||
) as HTMLSpanElement[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initNav(): void {
|
|
||||||
const s = state.get()
|
|
||||||
// init threshold text
|
|
||||||
updateThresholdText(expand(s.threshold))
|
|
||||||
// init index text
|
|
||||||
updateIndexText(expand(s.index + 1), expand(s.length))
|
|
||||||
// add watcher for updating nav text
|
|
||||||
state.addWatcher((o) => {
|
|
||||||
updateIndexText(expand(o.index + 1), expand(o.length))
|
|
||||||
updateThresholdText(expand(o.threshold))
|
|
||||||
})
|
|
||||||
|
|
||||||
// event listeners
|
|
||||||
decButton.addEventListener(
|
|
||||||
'click',
|
|
||||||
() => {
|
|
||||||
decThreshold()
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
incButton.addEventListener(
|
|
||||||
'click',
|
|
||||||
() => {
|
|
||||||
incThreshold()
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper
|
|
||||||
|
|
||||||
export function updateThresholdText(thresholdValue: string): void {
|
|
||||||
thresholdDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
|
||||||
e.innerText = thresholdValue[i]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateIndexText(indexValue: string, indexLength: string): void {
|
|
||||||
indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
|
||||||
if (i < 4) {
|
|
||||||
e.innerText = indexValue[i]
|
|
||||||
} else {
|
|
||||||
e.innerText = indexLength[i - 4]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,10 @@ export interface ImageJSON {
|
|||||||
hiImgW: number
|
hiImgW: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initResources(): Promise<ImageJSON[]> {
|
export async function getImageJSON(): Promise<ImageJSON[]> {
|
||||||
|
if (document.title.split(' | ')[0] === '404') {
|
||||||
|
return [] // no images on 404 page
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${window.location.href}index.json`, {
|
const response = await fetch(`${window.location.href}index.json`, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import { Watchable, decrement, increment } from './utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* types
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type State = typeof defaultState
|
|
||||||
|
|
||||||
/**
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
const thresholds = [
|
|
||||||
{ threshold: 20, trailLength: 20 },
|
|
||||||
{ threshold: 40, trailLength: 10 },
|
|
||||||
{ threshold: 80, trailLength: 5 },
|
|
||||||
{ threshold: 140, trailLength: 5 },
|
|
||||||
{ threshold: 200, trailLength: 5 }
|
|
||||||
]
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
index: -1,
|
|
||||||
length: 0,
|
|
||||||
threshold: thresholds[getThresholdSessionIndex()].threshold,
|
|
||||||
trailLength: thresholds[getThresholdSessionIndex()].trailLength
|
|
||||||
}
|
|
||||||
|
|
||||||
export const state = new Watchable<State>(defaultState)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function initState(length: number): void {
|
|
||||||
const s = state.get()
|
|
||||||
s.length = length
|
|
||||||
updateThreshold(s, 0)
|
|
||||||
state.set(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setIndex(index: number): void {
|
|
||||||
const s = state.get()
|
|
||||||
s.index = index
|
|
||||||
state.set(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function incIndex(): void {
|
|
||||||
const s = state.get()
|
|
||||||
s.index = increment(s.index, s.length)
|
|
||||||
state.set(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decIndex(): void {
|
|
||||||
const s = state.get()
|
|
||||||
s.index = decrement(s.index, s.length)
|
|
||||||
state.set(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function incThreshold(): void {
|
|
||||||
let s = state.get()
|
|
||||||
s = updateThreshold(s, 1)
|
|
||||||
state.set(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decThreshold(): void {
|
|
||||||
let s = state.get()
|
|
||||||
s = updateThreshold(s, -1)
|
|
||||||
state.set(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper
|
|
||||||
*/
|
|
||||||
|
|
||||||
function updateThreshold(state: State, inc: number): State {
|
|
||||||
const i = thresholds.findIndex((t) => state.threshold === t.threshold) + inc
|
|
||||||
// out of bounds
|
|
||||||
if (i < 0 || i >= thresholds.length) return state
|
|
||||||
// storage the index so we can restore it even if we go to another page
|
|
||||||
sessionStorage.setItem('thresholdsIndex', i.toString())
|
|
||||||
const newItems = thresholds[i]
|
|
||||||
return { ...state, ...newItems }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThresholdSessionIndex(): number {
|
|
||||||
const s = sessionStorage.getItem('thresholdsIndex')
|
|
||||||
if (s === null) return 2
|
|
||||||
return parseInt(s)
|
|
||||||
}
|
|
||||||
136
assets/ts/state.tsx
Normal file
136
assets/ts/state.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
createSignal,
|
||||||
|
useContext,
|
||||||
|
type Accessor,
|
||||||
|
type JSX,
|
||||||
|
type Setter
|
||||||
|
} from 'solid-js'
|
||||||
|
import invariant from 'tiny-invariant'
|
||||||
|
|
||||||
|
import { decrement, getThresholdSessionIndex, increment } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interfaces and types
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ThresholdRelated {
|
||||||
|
threshold: number
|
||||||
|
trailLength: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
index: number
|
||||||
|
length: number
|
||||||
|
threshold: number
|
||||||
|
trailLength: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StateContextType = readonly [
|
||||||
|
Accessor<State>,
|
||||||
|
{
|
||||||
|
readonly setIndex: (index: number) => void
|
||||||
|
readonly incIndex: () => void
|
||||||
|
readonly decIndex: () => void
|
||||||
|
readonly incThreshold: () => void
|
||||||
|
readonly decThreshold: () => void
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
const thresholds: ThresholdRelated[] = [
|
||||||
|
{ threshold: 20, trailLength: 20 },
|
||||||
|
{ threshold: 40, trailLength: 10 },
|
||||||
|
{ threshold: 80, trailLength: 5 },
|
||||||
|
{ threshold: 140, trailLength: 5 },
|
||||||
|
{ threshold: 200, trailLength: 5 }
|
||||||
|
]
|
||||||
|
const makeStateContext: (
|
||||||
|
state: Accessor<State>,
|
||||||
|
setState: Setter<State>
|
||||||
|
) => StateContextType = (state: Accessor<State>, setState: Setter<State>) => {
|
||||||
|
return [
|
||||||
|
state,
|
||||||
|
{
|
||||||
|
setIndex: (index: number) => {
|
||||||
|
setState((s) => {
|
||||||
|
return { ...s, index }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
incIndex: () => {
|
||||||
|
setState((s) => {
|
||||||
|
return { ...s, index: increment(s.index, s.length) }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
decIndex: () => {
|
||||||
|
setState((s) => {
|
||||||
|
return { ...s, index: decrement(s.index, s.length) }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
incThreshold: () => {
|
||||||
|
setState((s) => {
|
||||||
|
return { ...s, ...updateThreshold(s.threshold, thresholds, 1) }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
decThreshold: () => {
|
||||||
|
setState((s) => {
|
||||||
|
return { ...s, ...updateThreshold(s.threshold, thresholds, -1) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
}
|
||||||
|
const StateContext = createContext<StateContextType>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
function updateThreshold(
|
||||||
|
currentThreshold: number,
|
||||||
|
thresholds: ThresholdRelated[],
|
||||||
|
stride: number
|
||||||
|
): ThresholdRelated {
|
||||||
|
const i = thresholds.findIndex((t) => t.threshold === currentThreshold) + stride
|
||||||
|
if (i < 0 || i >= thresholds.length) return thresholds[i - stride]
|
||||||
|
// storage the index so we can restore it even if we go to another page
|
||||||
|
sessionStorage.setItem('thresholdsIndex', i.toString())
|
||||||
|
return thresholds[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StateProvider
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function StateProvider(props: {
|
||||||
|
children?: JSX.Element
|
||||||
|
length: number
|
||||||
|
}): JSX.Element {
|
||||||
|
const defaultState: State = {
|
||||||
|
index: -1,
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
length: props.length,
|
||||||
|
threshold: thresholds[getThresholdSessionIndex()].threshold,
|
||||||
|
trailLength: thresholds[getThresholdSessionIndex()].trailLength
|
||||||
|
}
|
||||||
|
|
||||||
|
const [state, setState] = createSignal(defaultState)
|
||||||
|
// eslint-disable-next-line solid/reactivity
|
||||||
|
const contextValue = makeStateContext(state, setState)
|
||||||
|
return (
|
||||||
|
<StateContext.Provider value={contextValue}>{props.children}</StateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use context
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useState(): StateContextType {
|
||||||
|
const uc = useContext(StateContext)
|
||||||
|
invariant(uc, 'undefined context')
|
||||||
|
return uc
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import { type Power3, type gsap } from 'gsap'
|
import { type gsap } from 'gsap'
|
||||||
import { type Swiper } from 'swiper'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* custom helpers
|
* types
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Vector = 'prev' | 'next' | 'none'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* utils
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function increment(num: number, length: number): number {
|
export function increment(num: number, length: number): number {
|
||||||
@@ -17,62 +22,18 @@ export function expand(num: number): string {
|
|||||||
return ('0000' + num.toString()).slice(-4)
|
return ('0000' + num.toString()).slice(-4)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMobile(): boolean {
|
export async function loadGsap(): Promise<typeof gsap> {
|
||||||
return window.matchMedia('(hover: none)').matches
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRandom(min: number, max: number): number {
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onVisible<T extends Element>(
|
|
||||||
element: T,
|
|
||||||
callback: (arg0: T) => void
|
|
||||||
): void {
|
|
||||||
new IntersectionObserver((entries, observer) => {
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
if (entry.intersectionRatio > 0) {
|
|
||||||
callback(element)
|
|
||||||
observer.disconnect()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).observe(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function capitalizeFirstLetter(str: string): string {
|
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadGsap(): Promise<[typeof gsap, typeof Power3]> {
|
|
||||||
const g = await import('gsap')
|
const g = await import('gsap')
|
||||||
return [g.gsap, g.Power3]
|
return g.gsap
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadSwiper(): Promise<typeof Swiper> {
|
export function getThresholdSessionIndex(): number {
|
||||||
const s = await import('swiper')
|
const s = sessionStorage.getItem('thresholdsIndex')
|
||||||
return s.Swiper
|
if (s === null) return 2
|
||||||
|
return parseInt(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function removeDuplicates<T>(arr: T[]): T[] {
|
||||||
* custom types
|
if (arr.length < 2) return arr // optimization
|
||||||
*/
|
return [...new Set(arr)]
|
||||||
|
|
||||||
export class Watchable<T> {
|
|
||||||
constructor(private obj: T) {}
|
|
||||||
private readonly watchers: Array<(arg0: T) => void> = []
|
|
||||||
|
|
||||||
get(): T {
|
|
||||||
return this.obj
|
|
||||||
}
|
|
||||||
|
|
||||||
set(e: T): void {
|
|
||||||
this.obj = e
|
|
||||||
this.watchers.forEach((watcher) => {
|
|
||||||
watcher(this.obj)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
addWatcher(watcher: (arg0: T) => void): void {
|
|
||||||
this.watchers.push(watcher)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ The minimum required Hugo version can be seen in the [`theme.toml`](https://gith
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Git
|
### Git (for adavanced user)
|
||||||
|
|
||||||
On the main branch, you can find the theme's latest source code. To use the latest version, you can clone the repository to `themes/bridget` by running the following command in the root directory of your Hugo site:
|
On the main branch, you can find the theme's latest source code. To use the latest version, you can clone the repository to `themes/bridget` by running the following command in the root directory of your Hugo site:
|
||||||
|
|
||||||
@@ -36,11 +36,13 @@ If you are already using Git for your site, you can add the theme as a submodule
|
|||||||
git submodule add https://github.com/Sped0n/bridget themes/bridget
|
git submodule add https://github.com/Sped0n/bridget themes/bridget
|
||||||
```
|
```
|
||||||
|
|
||||||
After cloning/downloading theme files to the directory, if you want to customize the theme, please run `pnpm install` or `npm install` first.
|
> ⚠️⚠️⚠️
|
||||||
|
>
|
||||||
|
> Please refer to the config section for the following content.
|
||||||
|
|
||||||
### Module (recommended)
|
### Module (recommended)
|
||||||
|
|
||||||
> If you want to modify the theme, use Git installation instead.
|
> If you want to have some customizations, use Git installation instead.
|
||||||
|
|
||||||
This theme is also available as a [Hugo module](https://gohugo.io/hugo-modules/). Run the following command in the root directory of your Hugo site:
|
This theme is also available as a [Hugo module](https://gohugo.io/hugo-modules/). Run the following command in the root directory of your Hugo site:
|
||||||
|
|
||||||
@@ -48,7 +50,7 @@ First turn your site into a Hugo module (in case you haven't done it yet):
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
hugo mod init github.com/me/my-new-site
|
hugo mod init github.com/me/my-new-site
|
||||||
# or whatever you like, it doesn’t necessarily have to be a GitHub link.
|
# or whatever you like, it doesn’t necessarily have to be a GitHub repo link.
|
||||||
hugo mod init blablabla
|
hugo mod init blablabla
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ Then import the theme as a dependency adding the following line to the `module`
|
|||||||
# config/_default/hugo.toml
|
# config/_default/hugo.toml
|
||||||
[module]
|
[module]
|
||||||
[[module.imports]]
|
[[module.imports]]
|
||||||
path = "github.com/Sped0n/bridget"
|
path = "github.com/Sped0n/bridget/v2"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to upgrade the theme, just run:
|
If you want to upgrade the theme, just run:
|
||||||
@@ -67,6 +69,10 @@ If you want to upgrade the theme, just run:
|
|||||||
hugo mod get -u
|
hugo mod get -u
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> ⚠️⚠️⚠️
|
||||||
|
>
|
||||||
|
> Please refer to the config section for the following content.
|
||||||
|
|
||||||
## Content Management
|
## Content Management
|
||||||
|
|
||||||
The content is where the pictures/text is stored, while the static refers to the website icons.
|
The content is where the pictures/text is stored, while the static refers to the website icons.
|
||||||
@@ -106,6 +112,8 @@ menu:
|
|||||||
identifier: Erwitt
|
identifier: Erwitt
|
||||||
title: Erwitt
|
title: Erwitt
|
||||||
unifiedAlt: '© Elliott Erwitt'
|
unifiedAlt: '© Elliott Erwitt'
|
||||||
|
_build:
|
||||||
|
publishResources: false
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -123,6 +131,8 @@ unifiedAlt: '© Elliott Erwitt'
|
|||||||
|
|
||||||
- `unifiedAlt` is **optional**, If you left it empty, the alt attribute of the image will default to its file name; if it is set, the alt attributes of all images will be unified to the value you have set;
|
- `unifiedAlt` is **optional**, If you left it empty, the alt attribute of the image will default to its file name; if it is set, the alt attributes of all images will be unified to the value you have set;
|
||||||
|
|
||||||
|
- `publishResources` is **optional but recommended**, setting it to false will hide unprocessed images in the `public` directory. By default, Hugo’s value for this option is true, which can potentially result in source image leakage.
|
||||||
|
|
||||||
- If this is a **showcase** page, simply place the images in the same directory as index.md.
|
- If this is a **showcase** page, simply place the images in the same directory as index.md.
|
||||||
|
|
||||||
- If this is an **information** page, you can continue writing the information you want to display in index.md.
|
- If this is an **information** page, you can continue writing the information you want to display in index.md.
|
||||||
@@ -152,13 +162,17 @@ We will focus on introducing the part about `theme as module`, detailed comments
|
|||||||
```toml
|
```toml
|
||||||
# theme as module
|
# theme as module
|
||||||
[module]
|
[module]
|
||||||
replacements = "github.com/Sped0n/bridget -> ../.."
|
replacements = "github.com/Sped0n/bridget/v2 -> ../.."
|
||||||
[[module.imports]]
|
[[module.imports]]
|
||||||
path = "github.com/Sped0n/bridget"
|
path = "github.com/Sped0n/bridget/v2"
|
||||||
```
|
```
|
||||||
|
|
||||||
- If you want to <u>modify the theme</u> or you have <u>installation with Git</u>, please **keep the `replacements` configuration** and change the path after the arrow to the location of your local theme file (relative path only).
|
- If you have _installation with Git_
|
||||||
- If you have <u>installation with Module</u>, **remove the `replacements` configuration**.
|
|
||||||
|
- `replacement`: replace the _path after the arrow_(`../..`) with the location of your local theme file (⚠️⚠️⚠️**relative path to hugo site theme directory only([official doc](https://gohugo.io/hugo-modules/configuration/#module-configuration-top-level))**, example: `bridget`)
|
||||||
|
- `path`: no change
|
||||||
|
|
||||||
|
- If you have _installation with Module_, **remove the `replacements` configuration**.
|
||||||
|
|
||||||
### `markup.toml`
|
### `markup.toml`
|
||||||
|
|
||||||
@@ -168,10 +182,16 @@ path = "github.com/Sped0n/bridget"
|
|||||||
|
|
||||||
Detailed description in the comments.
|
Detailed description in the comments.
|
||||||
|
|
||||||
> ⚠️⚠️⚠️
|
|
||||||
>
|
|
||||||
> Only thing that you need to pay **extra attention** is the [`bundled`](https://github.com/Sped0n/bridget/blob/1e2f1fadde9c16989eef1ab771f2ac8463dec5a4/exampleSite/config/_default/params.toml#L6) option, please read the corresponding doc and set it as your need.
|
|
||||||
|
|
||||||
### `sitemap.toml`
|
### `sitemap.toml`
|
||||||
|
|
||||||
https://gohugo.io/templates/sitemap-template/#configuration
|
https://gohugo.io/templates/sitemap-template/#configuration
|
||||||
|
|
||||||
|
## Customization (AKA for developer)
|
||||||
|
|
||||||
|
> Before heading to this section, please make sure you have **installation with Git**.
|
||||||
|
>
|
||||||
|
> You can use any package manager you want (npm/pnpm/yarn/bun).
|
||||||
|
|
||||||
|
- run `pnpm install` to install neceessary dependencies.
|
||||||
|
- run `pnpm run dev` to host a dev server.
|
||||||
|
- when you’re ready, run `pnpm run build` to update artifacts.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# timeout
|
# timeout
|
||||||
timeout = "1200s"
|
timeout = "1200s"
|
||||||
# your website url
|
# your website url
|
||||||
baseURL = 'https://www.example.com/'
|
baseURL = 'https://bridget-demo.sped0n.com'
|
||||||
# website title
|
# website title
|
||||||
title = 'Bridget'
|
title = 'Bridget'
|
||||||
# don't touch this
|
# don't touch this
|
||||||
@@ -22,6 +22,6 @@ defaultContentLanguage = 'en'
|
|||||||
|
|
||||||
# theme as module
|
# theme as module
|
||||||
[module]
|
[module]
|
||||||
replacements = "github.com/Sped0n/bridget -> ../.." # deploy with local dir WARN: delete this line if you want to deploy with git
|
replacements = "github.com/Sped0n/bridget/v2 -> ../.." # deploy with local dir (relative to hugo site theme dir) WARN: delete this line if you want to deploy with git
|
||||||
[[module.imports]]
|
[[module.imports]]
|
||||||
path = "github.com/Sped0n/bridget" # deploy with git (recommended) WARN: you should also set `bundled` to true in params.toml !!!
|
path = "github.com/Sped0n/bridget/v2" # deploy with git (recommended) WARN: you should also set `bundled` to true in params.toml !!!
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
# description of the site (will be placed in meta)
|
# description of the site (will be placed in meta)
|
||||||
description = "Bridget is a minimal Hugo theme designed for photographers/visual artists."
|
description = "Bridget is a minimal Hugo theme designed for photographers/visual artists."
|
||||||
# use bundled js and css
|
|
||||||
# * if you want to build the js and css from scratch, set this to false and run `npm install` and `npm run build`
|
|
||||||
# * tldr: set this to false if you want to develop and edit the js and css
|
|
||||||
bundled = false
|
|
||||||
|
|
||||||
# whether to use favicon resource links
|
# whether to use favicon resource links
|
||||||
# generate these with https://realfavicongenerator.net
|
# generate these with https://realfavicongenerator.net
|
||||||
@@ -51,9 +47,9 @@ id = ""
|
|||||||
# Umami Analytics
|
# Umami Analytics
|
||||||
[analytics.umami]
|
[analytics.umami]
|
||||||
data_website_id = "44a4a42d-ec8e-44c9-a38c-7533929e9845"
|
data_website_id = "44a4a42d-ec8e-44c9-a38c-7533929e9845"
|
||||||
src = "https://umami.sped0nwen.com/script.js"
|
src = "https://umami.sped0n.com/script.js"
|
||||||
data_host_url = ""
|
data_host_url = ""
|
||||||
data_domains = "bridget-demo.sped0nwen.com"
|
data_domains = "bridget-demo.sped0n.com"
|
||||||
# Plausible Analytics
|
# Plausible Analytics
|
||||||
[analytics.plausible]
|
[analytics.plausible]
|
||||||
data_domain = ""
|
data_domain = ""
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ menu:
|
|||||||
identifier: Erwitt
|
identifier: Erwitt
|
||||||
title: Erwitt
|
title: Erwitt
|
||||||
unifiedAlt: '© Elliott Erwitt'
|
unifiedAlt: '© Elliott Erwitt'
|
||||||
|
_build:
|
||||||
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ menu:
|
|||||||
identifier: Gruyaert
|
identifier: Gruyaert
|
||||||
title: Gruyaert
|
title: Gruyaert
|
||||||
unifiedAlt: '© Harry Gruyaert'
|
unifiedAlt: '© Harry Gruyaert'
|
||||||
|
_build:
|
||||||
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ menu:
|
|||||||
identifier: Info
|
identifier: Info
|
||||||
title: Info
|
title: Info
|
||||||
unifiedAlt: ''
|
unifiedAlt: ''
|
||||||
|
_build:
|
||||||
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|
||||||
Bridget is a _minimal_ Hugo theme designed for photographers/visual artists.
|
Bridget is a _minimal_ Hugo theme designed for photographers/visual artists, powered by <u>[Solid.js](https://www.solidjs.com)</u>.
|
||||||
|
|
||||||
The inspiration for this theme came from a video by <u>[Hyperlexed](https://www.youtube.com/@Hyperplexed)</u>, which can be found <u>[here](https://www.youtube.com/watch?v=Jt3A2lNN2aE)</u>. Initially, it was developed using raw TypeScript and CSS. However, after website designer <u>[Tyler McRobert](https://tylermcrobert.com)</u> made the source code publicly available, I realized that I have invented many unnecessary components, and this project was modified to porting the original design to hugo while focussing on _performance_.
|
The inspiration for this theme came from a video by <u>[Hyperlexed](https://www.youtube.com/@Hyperplexed)</u>, which can be found <u>[here](https://www.youtube.com/watch?v=Jt3A2lNN2aE)</u>. Initially, it was developed using no third-party dependencies. However, after website designer <u>[Tyler McRobert](https://tylermcrobert.com)</u> made the source code publicly available, I realized that I have invented many unnecessary wheels, and this project was modified to porting the original design to Hugo while focusing on _performance_.
|
||||||
|
|
||||||
Once again, great shout out to <u>[Tyler McRobert](https://tylermcrobert.com)</u> for his inspiration to this project.
|
Once again, great shout out to <u>[Tyler McRobert](https://tylermcrobert.com)</u> for his inspiration to this project.
|
||||||
|
|
||||||
[Repo ↗](https://github.com/Sped0n/bridget)
|
[GitHub Repo ↗](https://github.com/Sped0n/bridget)
|
||||||
|
|
||||||
Original site design by <u>[Tyler McRobert](https://tylermcrobert.com)</u>.
|
Original site design by <u>[Tyler McRobert](https://tylermcrobert.com)</u>.
|
||||||
|
|
||||||
© {{< year >}} <u>[Spedon](https://github.com/Sped0n)</u> | Powered by [Hugo](https://gohugo.io)
|
© {{< year >}} <u>[Spedon](https://github.com/Sped0n)</u> | Built with Hugo
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ menu:
|
|||||||
identifier: Webb
|
identifier: Webb
|
||||||
title: Webb
|
title: Webb
|
||||||
unifiedAlt: '© Alex Webb'
|
unifiedAlt: '© Alex Webb'
|
||||||
|
_build:
|
||||||
|
publishResources: false
|
||||||
---
|
---
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,3 +1,3 @@
|
|||||||
module github.com/Sped0n/bridget
|
module github.com/Sped0n/bridget/v2
|
||||||
|
|
||||||
go 1.21.3
|
go 1.21.3
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "schließen"
|
|||||||
other = "schwelle"
|
other = "schwelle"
|
||||||
[404]
|
[404]
|
||||||
other = "seite nicht gefunden"
|
other = "seite nicht gefunden"
|
||||||
|
[loading]
|
||||||
|
other = "lade..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "close"
|
|||||||
other = "threshold"
|
other = "threshold"
|
||||||
[404]
|
[404]
|
||||||
other = "page not found"
|
other = "page not found"
|
||||||
|
[loading]
|
||||||
|
other = "loading..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "cerrar"
|
|||||||
other = "umbral"
|
other = "umbral"
|
||||||
[404]
|
[404]
|
||||||
other = "página no encontrada"
|
other = "página no encontrada"
|
||||||
|
[loading]
|
||||||
|
other = "cargando..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "fermer"
|
|||||||
other = "seuil"
|
other = "seuil"
|
||||||
[404]
|
[404]
|
||||||
other = "page non trouvée"
|
other = "page non trouvée"
|
||||||
|
[loading]
|
||||||
|
other = "chargement..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "chiudi"
|
|||||||
other = "soglia"
|
other = "soglia"
|
||||||
[404]
|
[404]
|
||||||
other = "pagina non trovata"
|
other = "pagina non trovata"
|
||||||
|
[loading]
|
||||||
|
other = "caricamento..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "閉じる"
|
|||||||
other = "しきい値"
|
other = "しきい値"
|
||||||
[404]
|
[404]
|
||||||
other = "ページが見つかりません"
|
other = "ページが見つかりません"
|
||||||
|
[loading]
|
||||||
|
other = "読み込み中..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "닫기"
|
|||||||
other = "임계값"
|
other = "임계값"
|
||||||
[404]
|
[404]
|
||||||
other = "페이지를 찾을 수 없습니다"
|
other = "페이지를 찾을 수 없습니다"
|
||||||
|
[loading]
|
||||||
|
other = "로딩중..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "关闭"
|
|||||||
other = "阈值"
|
other = "阈值"
|
||||||
[404]
|
[404]
|
||||||
other = "页面不存在"
|
other = "页面不存在"
|
||||||
|
[loading]
|
||||||
|
other = "加载中..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "關閉"
|
|||||||
other = "閾值"
|
other = "閾值"
|
||||||
[404]
|
[404]
|
||||||
other = "找不到頁面"
|
other = "找不到頁面"
|
||||||
|
[loading]
|
||||||
|
other = "載入中..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "關閉"
|
|||||||
other = "閾值"
|
other = "閾值"
|
||||||
[404]
|
[404]
|
||||||
other = "找不到頁面"
|
other = "找不到頁面"
|
||||||
|
[loading]
|
||||||
|
other = "載入中..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "关闭"
|
|||||||
other = "阈值"
|
other = "阈值"
|
||||||
[404]
|
[404]
|
||||||
other = "页面不存在"
|
other = "页面不存在"
|
||||||
|
[loading]
|
||||||
|
other = "加载中..."
|
||||||
|
|||||||
@@ -8,3 +8,5 @@ other = "關閉"
|
|||||||
other = "閾值"
|
other = "閾值"
|
||||||
[404]
|
[404]
|
||||||
other = "找不到頁面"
|
other = "找不到頁面"
|
||||||
|
[loading]
|
||||||
|
other = "載入中..."
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{{- define "main" -}}
|
{{- define "main" -}}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{- partial "nav.html" . -}}
|
{{- partial "nav.html" . -}}
|
||||||
</div>
|
<article>
|
||||||
<article class="info">
|
|
||||||
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
||||||
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
||||||
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
<p class="error">⛝ <u>404</u> {{- i18n 404 -}} ⛝</p>
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
{{- define "main" -}}
|
{{- define "main" -}}
|
||||||
<script>
|
<div
|
||||||
document.getElementById("main").setAttribute("nextText", "{{- i18n "next" -}}")
|
class="container"
|
||||||
document.getElementById("main").setAttribute("prevText", "{{- i18n "prev" -}}")
|
data-next="{{- i18n "next" -}}"
|
||||||
document.getElementById("main").setAttribute("closeText", "{{- i18n "close" -}}")
|
data-prev="{{- i18n "prev" -}}"
|
||||||
</script>
|
data-close="{{- i18n "close" -}}"
|
||||||
<div class="container">
|
data-loading="{{- i18n "loading" -}}"
|
||||||
{{- partial "nav.html" . -}}
|
>
|
||||||
</div>
|
{{- with .Content -}}
|
||||||
<article class="info">
|
<article>
|
||||||
{{ .Content }}
|
{{- . -}}
|
||||||
</article>
|
</article>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- partial "nav.html" . -}}
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
{{- $Page := . -}}
|
{{- $context := . -}}
|
||||||
{{- $Path := "" -}}
|
|
||||||
{{- $params := .Page.Params | merge .Site.Params.Page -}}
|
{{- $params := .Page.Params | merge .Site.Params.Page -}}
|
||||||
|
|
||||||
{{- with partial "function/currentMenuItem.html" . -}}
|
{{- with partial "function/getImageSlice.html" . -}}
|
||||||
{{- $Path = .Identifier -}}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{- $gallery := site.GetPage $Path -}}
|
|
||||||
{{- with $gallery.Resources.ByType "image" -}}
|
|
||||||
{{- $index := len . -}}
|
{{- $index := len . -}}
|
||||||
{{- $Page.Scratch.Add "img" slice -}}
|
{{- $context.Scratch.Add "img" slice -}}
|
||||||
{{- range sort . "Name" "desc" -}}
|
{{- range sort . "Name" "desc" -}}
|
||||||
{{- $image := . -}}
|
{{- $image := . -}}
|
||||||
{{- $index = sub $index 1 -}}
|
{{- $index = sub $index 1 -}}
|
||||||
@@ -19,7 +13,7 @@
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- $lores := .Resize (site.Params.loResOpt | default "700x webp Lanczos q60") -}}
|
{{- $lores := .Resize (site.Params.loResOpt | default "700x webp Lanczos q60") -}}
|
||||||
{{- $hires := .Resize (site.Params.hiResOpt | default "2000x webp Lanczos q75") -}}
|
{{- $hires := .Resize (site.Params.hiResOpt | default "2000x webp Lanczos q75") -}}
|
||||||
{{- $Page.Scratch.Add "img" (dict
|
{{- $context.Scratch.Add "img" (dict
|
||||||
"index" (int $index)
|
"index" (int $index)
|
||||||
"alt" (string $alt)
|
"alt" (string $alt)
|
||||||
"loUrl" (string $lores.RelPermalink)
|
"loUrl" (string $lores.RelPermalink)
|
||||||
@@ -31,7 +25,7 @@
|
|||||||
)
|
)
|
||||||
-}}
|
-}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{ $Page.Scratch.Get "img" | jsonify }}
|
{{ $context.Scratch.Get "img" | jsonify }}
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
[]
|
[]
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{{- $currentPage := . -}}
|
{{- $currentPage := . -}}
|
||||||
|
|
||||||
{{- $identifier := "" -}}
|
{{- $identifier := "" -}}
|
||||||
{{- $title := "" -}}
|
{{- $title := "404" -}}
|
||||||
{{- $weight := -1 -}}
|
{{- $weight := -1 -}}
|
||||||
|
|
||||||
{{- range site.Menus.main -}}
|
{{- range site.Menus.main -}}
|
||||||
|
|||||||
10
layouts/partials/function/getImageSlice.html
Normal file
10
layouts/partials/function/getImageSlice.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{{- $context := . -}}
|
||||||
|
{{- $Path := "" -}}
|
||||||
|
{{- $params := .Page.Params | merge .Site.Params.Page -}}
|
||||||
|
|
||||||
|
{{- with partial "function/currentMenuItem.html" . -}}
|
||||||
|
{{- $Path = .Identifier -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $gallery := site.GetPage $Path -}}
|
||||||
|
{{- return $gallery.Resources.ByType "image" -}}
|
||||||
@@ -7,16 +7,18 @@
|
|||||||
{{- $style = dict "Context" . "ToCSS" $options "Inline" true | merge $style -}}
|
{{- $style = dict "Context" . "ToCSS" $options "Inline" true | merge $style -}}
|
||||||
{{- partial "plugin/style.html" $style -}}
|
{{- partial "plugin/style.html" $style -}}
|
||||||
|
|
||||||
{{/* main style */}}
|
{{- $style := dict "Link" "/bundled/css/main.css" "Defer" true -}}
|
||||||
{{- if (site.Params.bundled | default true) -}}
|
|
||||||
{{- $style := dict "Link" "/bundled/css/style.min.css" "Defer" true -}}
|
|
||||||
{{- partial "plugin/style.html" $style -}}
|
{{- partial "plugin/style.html" $style -}}
|
||||||
{{- else -}}
|
|
||||||
{{- $style := dict "Source" "scss/style.scss" "Fingerprint" $fingerprint -}}
|
{{/* fuck safari */}}
|
||||||
{{- $options := dict "targetPath" "css/style.css" "enableSourceMap" true "includePaths" (slice "node_modules") -}}
|
<script>
|
||||||
{{- $style = dict "Context" . "ToCSS" $options "Minify" hugo.IsProduction "Defer" true | merge $style -}}
|
function z() {
|
||||||
{{- partial "plugin/style.html" $style -}}
|
const r = document.querySelector(':root')
|
||||||
{{- end -}}
|
r.style.setProperty('--window-height', `${window.innerHeight}px`)
|
||||||
|
}
|
||||||
|
z()
|
||||||
|
window.addEventListener('resize', z, { passive: true })
|
||||||
|
</script>
|
||||||
|
|
||||||
{{/* main js */}}
|
{{/* main js */}}
|
||||||
{{- $script := dict "Link" "/bundled/js/main.js" "Defer" true "Esm" true -}}
|
{{- $script := dict "Link" "/bundled/js/main.js" "Defer" true "Esm" true -}}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
</div>
|
</div>
|
||||||
<div class="threshold">
|
<div class="threshold">
|
||||||
|
{{- $length := 0 -}}
|
||||||
|
{{- with partial "function/getImageSlice.html" . -}}
|
||||||
|
{{- $length = len . -}}
|
||||||
|
{{- end -}}
|
||||||
<span>{{- i18n "threshold" | strings.FirstUpper -}}:</span>
|
<span>{{- i18n "threshold" | strings.FirstUpper -}}:</span>
|
||||||
<span>
|
<span>
|
||||||
<button class="dec">-</button>
|
<button class="dec">-</button>
|
||||||
@@ -42,10 +46,43 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="index">
|
<div class="index">
|
||||||
<span class="num"></span><span class="num"></span><span class="num"></span
|
<span class="num">0</span><span class="num">0</span><span class="num">0</span
|
||||||
><span class="num"></span>
|
><span class="num">0</span>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span class="num"></span><span class="num"></span><span class="num"></span
|
<span class="num">{{- math.Floor (div $length 1000) -}}</span
|
||||||
><span class="num"></span>
|
><span class="num">{{- mod (math.Floor (div $length 100)) 10 -}}</span
|
||||||
|
><span class="num">{{- mod (math.Floor (div $length 10)) 10 -}}</span
|
||||||
|
><span class="num">{{- mod $length 10 -}}</span>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
{{- /* for threshold */ -}}
|
||||||
|
<script>
|
||||||
|
i = 2
|
||||||
|
const s = sessionStorage.getItem('thresholdsIndex')
|
||||||
|
if (s !== null) {
|
||||||
|
i = parseInt(s)
|
||||||
|
}
|
||||||
|
var t = ''
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
t = '0020'
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
t = '0040'
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
t = '0080'
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
t = '0140'
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
t = '0200'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Array.from(
|
||||||
|
document.getElementsByClassName('threshold').item(0).getElementsByClassName('num')
|
||||||
|
).forEach((e, i) => {
|
||||||
|
e.innerText = t[i]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
53
package.json
53
package.json
@@ -1,19 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "bridget",
|
"name": "bridget",
|
||||||
"version": "v0.0.4",
|
"version": "v1.0.0",
|
||||||
|
"type": "module",
|
||||||
"description": "bridget theme source file",
|
"description": "bridget theme source file",
|
||||||
"packageManager": "pnpm@8.10.2",
|
"packageManager": "pnpm@8.10.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"vite": "vite build --no-watch",
|
||||||
"lint": "eslint . --fix && prettier --write .",
|
"lint": "eslint . --fix && prettier --write .",
|
||||||
"lint:check": "eslint . && prettier . --check",
|
"lint:check": "eslint . && prettier . --check",
|
||||||
"dev": "run-p rollup:dev hugo:dev",
|
"dev": "run-p vite:dev hugo:dev",
|
||||||
"build": "rm -f ./static/bundled/js/* && run-s rollup:build hugo:build && yes | cp -rf ./exampleSite/public/css/* ./static/bundled/css",
|
"build": "run-s vite:build hugo:build",
|
||||||
"server": "run-p rollup:server hugo:server",
|
"server": "run-p vite:server hugo:server",
|
||||||
"rollup:build": "rollup -c --environment BUILD:production",
|
"vite:build": "vite build --no-watch --minify terser",
|
||||||
"rollup:server": "rollup -c --watch --environment BUILD:production",
|
"vite:server": "vite build --minify terser",
|
||||||
"rollup:dev": "rollup -c --watch --environment BUILD:development",
|
"vite:dev": "vite build --mode development --minify false",
|
||||||
"hugo:build": "hugo --logLevel info --source=exampleSite --gc",
|
"hugo:build": "hugo --logLevel info --source=exampleSite --gc",
|
||||||
"hugo:preview": "hugo --logLevel info --source=exampleSite -D --gc",
|
"hugo:preview": "hugo --logLevel info --source=exampleSite -D --gc",
|
||||||
"hugo:dev": "hugo server --source=exampleSite --gc -D --disableFastRender --watch --logLevel info",
|
"hugo:dev": "hugo server --source=exampleSite --gc -D --disableFastRender --watch --logLevel info",
|
||||||
@@ -37,29 +39,32 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Sped0n/bridget#readme",
|
"homepage": "https://github.com/Sped0n/bridget#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
"@typescript-eslint/parser": "^6.17.0",
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"eslint-config-standard": "^17.1.0",
|
||||||
"eslint-config-standard-with-typescript": "^43.0.0",
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.3",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-n": "^16.6.1",
|
"eslint-plugin-n": "^16.6.2",
|
||||||
"eslint-plugin-prettier": "^5.1.2",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-promise": "^6.6.0",
|
||||||
|
"eslint-plugin-solid": "^0.14.2",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "3.1.1",
|
"prettier": "3.3.3",
|
||||||
"prettier-plugin-go-template": "^0.0.15",
|
"prettier-plugin-go-template": "^0.0.15",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3"
|
"sass": "^1.77.8",
|
||||||
|
"terser": "^5.31.6",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vite": "^5.4.2",
|
||||||
|
"vite-plugin-solid": "^2.10.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gsap": "^3.12.4",
|
"gsap": "^3.12.5",
|
||||||
"swiper": "^11.0.5",
|
"solid-js": "^1.8.22",
|
||||||
"rollup": "^4.9.3",
|
"swiper": "^11.1.11",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"tiny-invariant": "^1.3.3"
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
|
||||||
"@rollup/plugin-typescript": "^11.1.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1679
pnpm-lock.yaml
generated
1679
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
|||||||
import resolve from '@rollup/plugin-node-resolve'
|
|
||||||
import terser from '@rollup/plugin-terser'
|
|
||||||
import typescript from '@rollup/plugin-typescript'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
input: './assets/ts/main.ts',
|
|
||||||
output: {
|
|
||||||
dir: './static/bundled/js',
|
|
||||||
format: 'es',
|
|
||||||
chunkFileNames: '[hash:6].js',
|
|
||||||
compact: true
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
resolve({
|
|
||||||
moduleDirectories: ['node_modules']
|
|
||||||
}),
|
|
||||||
typescript({ tsconfig: './tsconfig.json' }),
|
|
||||||
process.env.BUILD === 'production' &&
|
|
||||||
terser({
|
|
||||||
compress: {
|
|
||||||
passes: 3
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
comments: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
1
static/bundled/css/main.css
Normal file
1
static/bundled/css/main.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/bundled/css/style.min.css
vendored
1
static/bundled/css/style.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
static/bundled/js/B3pkGT.js
Normal file
1
static/bundled/js/B3pkGT.js
Normal file
File diff suppressed because one or more lines are too long
1
static/bundled/js/BSNAnE.js
Normal file
1
static/bundled/js/BSNAnE.js
Normal file
File diff suppressed because one or more lines are too long
1
static/bundled/js/CqsBem.js
Normal file
1
static/bundled/js/CqsBem.js
Normal file
File diff suppressed because one or more lines are too long
1
static/bundled/js/EYMnLv.js
Normal file
1
static/bundled/js/EYMnLv.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import{l as e,c as t,W as n,s as a,i as s,a as i,d as o,b as c}from"./main.js";let r=[],d={x:0,y:0};const g=new n([]),l=new n(!1),m=new n(!1),h=new n(!1);let u,p,f=!1;function v(){return g.get().map((e=>r[e.i]))}function y(){const e=v().slice(-a.get().trailLength);return e.slice(0,e.length-1)}function w(){const e=v();return e[e.length-1]}function I(){const e=g.get(),t=a.get(),n=e.length>0?e[e.length-1].i:t.index,i=[];for(let e=0;e<7;e++)i.push(r[s(n+e,t.length)]);return i}function E(){const e=g.get(),t=a.get();return r[o(e[e.length-1].i,t.length)]}function x(){const e=g.get(),t=a.get();return r[s(e[e.length-1].i,t.length)]}function L(e){if(l.get()||m.get()||!f)return;const t={x:e.clientX,y:e.clientY};if(Math.hypot(t.x-d.x,t.y-d.y)>a.get().threshold){d=t,i();const e={i:a.get().index,...t};g.set([...g.get(),e].slice(-a.get().length))}}function W(){if(m.get()||!f)return;l.set(!0),m.set(!0),k([w(),E(),x()]);const e=u.timeline();e.to(y(),{y:"+=20",ease:p.easeIn,stagger:.075,duration:.3,delay:.1,opacity:0}),e.to(w(),{x:0,y:0,ease:p.easeInOut,duration:.7,delay:.3}),e.to(w(),{delay:.1,scale:1,ease:p.easeInOut}),e.then((()=>{m.set(!1)})).catch((e=>{console.log(e)}))}function H(){if(m.get()||!f)return;l.set(!1),m.set(!0),N([w()]),N(y());const e=u.timeline();e.to(w(),{scale:.6,duration:.6,ease:p.easeInOut}),e.to(w(),{delay:.3,duration:.7,ease:p.easeInOut,x:g.get()[g.get().length-1].x-window.innerWidth/2,y:g.get()[g.get().length-1].y-window.innerHeight/2}),e.to(y(),{y:"-=20",ease:p.easeOut,stagger:-.1,duration:.3,opacity:1}),e.then((()=>{m.set(!1)})).catch((e=>{console.log(e)}))}function k(e){e.forEach((e=>{e.src=e.dataset.hiUrl,e.height=parseInt(e.dataset.hiImgH),e.width=parseInt(e.dataset.hiImgW)}))}function N(e){e.forEach((e=>{e.src=e.dataset.loUrl,e.height=parseInt(e.dataset.loImgH),e.width=parseInt(e.dataset.loImgW)}))}const b=document.createElement("div"),A=document.createElement("div");function O(e){const t=e.clientX,n=e.clientY;b.style.transform=`translate3d(${t}px, ${n}px, 0)`}function U(e){A.innerText=e}const T=document.getElementById("main"),S=[T.getAttribute("prevText"),T.getAttribute("closeText"),T.getAttribute("nextText")];function B(e){e===S[0]?$():e===S[1]?H():Y()}function X(e){if(!l.get()&&!m.get())switch(e.key){case"ArrowLeft":$();break;case"Escape":H();break;case"ArrowRight":Y()}}function Y(){m.get()||(g.set(g.get().map((e=>({...e,i:s(e.i,a.get().length)})))),i())}function $(){m.get()||(g.set(g.get().map((e=>({...e,i:o(e.i,a.get().length)})))),c())}function j(n){b.className="cursor",A.className="cursorInner",b.append(A),t.append(b),window.addEventListener("mousemove",O,{passive:!0}),h.addWatcher((e=>{e?b.classList.add("active"):b.classList.remove("active")})),function(n){!function(e){const n=document.createElement("div");n.className="stage";for(const t of e){const e=document.createElement("img");e.height=t.loImgH,e.width=t.loImgW,e.dataset.hiUrl=t.hiUrl,e.dataset.hiImgH=t.hiImgH.toString(),e.dataset.hiImgW=t.hiImgW.toString(),e.dataset.loUrl=t.loUrl,e.dataset.loImgH=t.loImgH.toString(),e.dataset.loImgW=t.loImgW.toString(),e.alt=t.alt,n.append(e)}t.append(n)}(n);const s=document.getElementsByClassName("stage").item(0);r=Array.from(s.getElementsByTagName("img")),s.addEventListener("click",(()=>{W()})),s.addEventListener("keydown",(()=>{W()})),window.addEventListener("mousemove",L,{passive:!0}),l.addWatcher((e=>{h.set(e&&!m.get())})),m.addWatcher((e=>{h.set(l.get()&&!e)})),g.addWatcher((e=>{!function(){const e=v();0!==e.length&&f&&(N(I()),u.set(e,{x:e=>g.get()[e].x-window.innerWidth/2,y:e=>g.get()[e].y-window.innerHeight/2,opacity:e=>e+1+a.get().trailLength<=g.get().length?0:1,zIndex:e=>e,scale:.6}),l.get()&&(N(v()),k([w(),E(),x()]),u.set(r,{opacity:0}),u.set(w(),{opacity:1,x:0,y:0,scale:1})))}()})),N(I()),window.addEventListener("mousemove",(()=>{e().then((e=>{u=e[0],p=e[1],f=!0})).catch((e=>{console.log(e)}))}),{once:!0,passive:!0})}(n),function(){const e=document.createElement("div");e.className="navOverlay";for(const t of S){const n=document.createElement("div");n.className="overlay",n.addEventListener("click",(()=>{B(t)}),{passive:!0}),n.addEventListener("keydown",(()=>{B(t)}),{passive:!0}),n.addEventListener("mouseover",(()=>{U(t)}),{passive:!0}),n.addEventListener("focus",(()=>{U(t)}),{passive:!0}),e.append(n)}h.addWatcher((()=>{h.get()?e.classList.add("active"):e.classList.remove("active")})),t.append(e),window.addEventListener("keydown",X,{passive:!0})}()}export{j as initDesktop};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import{W as e,s as t,e as n,l as a,f as s,g as c,h as o,j as l,c as m,o as i,k as d}from"./main.js";const r=new e(!1);let p,u,g,h;const E=new e(!1);let y,v,f,N=-1,w=[],x=[],I=[],B=!1;function T(){n.set(!0),I[t.get().index].scrollIntoView({block:"center",behavior:"auto"}),v.to(u,{y:"100%",ease:f.easeInOut,duration:1}),v.to(g,{opacity:0,duration:1.2,delay:.4})}function C(){const e=[];e.push(x[h.activeIndex]),e.push(x[Math.min(h.activeIndex+1,h.slides.length)]),e.push(x[Math.max(h.activeIndex-1,0)]);for(const t of e)t.src=t.dataset.src}let L=[];function k(e){c(e),!E.get()&&B&&(E.set(!0),C(),v.to(g,{opacity:1,duration:1}),v.to(u,{y:0,ease:f.easeInOut,duration:1,delay:.4}),setTimeout((()=>{n.set(!1),E.set(!1)}),1200))}function A(e){(function(e){!function(e){const t=document.createElement("div");t.className="collection";for(const[n,a]of e.entries()){const e=0!==n?d(-25,25):0,s=0!==n?d(-30,30):0,c=document.createElement("img");c.dataset.src=a.loUrl,c.height=a.loImgH,c.width=a.loImgW,c.alt=a.alt,c.style.transform=`translate3d(${e}%, ${s-50}%, 0)`,t.append(c)}m.append(t)}(e);const t=document.getElementsByClassName("collection").item(0);r.addWatcher((e=>{e?t.classList.remove("hidden"):t.classList.add("hidden")})),L=Array.from(t.getElementsByTagName("img")),L.forEach(((e,t)=>{e.addEventListener("click",(()=>{k(t)}),{passive:!0}),e.addEventListener("keydown",(()=>{k(t)}),{passive:!0}),i(e,(()=>{for(let e=0;e<5;e++){const n=t+e;n<0||n>L.length-1||(L[n].src=L[n].dataset.src)}}))}))})(e),function(e){!function(e){const t=document.createElement("div");t.className="swiper-wrapper";for(const n of e){const e=document.createElement("div");e.className="swiper-slide";const a=document.createElement("img");a.dataset.src=n.hiUrl,a.height=n.hiImgH,a.width=n.hiImgW,a.alt=n.alt,e.append(a),t.append(e)}const n=document.createElement("div");n.className="galleryInner",n.append(t);const a=document.createElement("div");a.insertAdjacentHTML("afterbegin",'<span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>\n <span>/</span>\n <span class="num"></span><span class="num"></span><span class="num"></span><span class="num"></span>');const s=document.createElement("div"),c=document.getElementById("main")?.getAttribute("closeText");s.innerText=l(c),s.addEventListener("click",(()=>{T()}),{passive:!0}),s.addEventListener("keydown",(()=>{T()}),{passive:!0});const o=document.createElement("div");o.className="nav",o.append(a,s);const i=document.createElement("div");i.className="gallery",i.append(n),i.append(o);const d=document.createElement("div");d.className="curtain",m.append(i,d)}(e),w=Array.from(document.getElementsByClassName("nav").item(0)?.getElementsByClassName("num")??[]),p=document.getElementsByClassName("galleryInner").item(0),u=document.getElementsByClassName("gallery").item(0),g=document.getElementsByClassName("curtain").item(0),x=Array.from(u.getElementsByTagName("img")),I=Array.from(document.getElementsByClassName("collection").item(0)?.getElementsByTagName("img")??[]),t.addWatcher((()=>{const e=t.get();var n;e.index!==N&&(n=e.index,C(),h.slideTo(n,0),function(){const e=o(t.get().index+1),n=o(t.get().length);w.forEach(((t,a)=>{t.innerText=a<4?e[a]:n[a-4]}))}(),N=e.index)})),r.addWatcher((e=>{e&&n.set(!0)})),window.addEventListener("touchstart",(()=>{a().then((e=>{v=e[0],f=e[1]})).catch((e=>{console.log(e)})),s().then((e=>{y=e,h=new y(p,{spaceBetween:20}),h.on("slideChange",(({realIndex:e})=>{c(e)}))})).catch((e=>{console.log(e)})),B=!0}),{once:!0,passive:!0}),r.set(!0)}(e)}export{A as initMobile};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -9,7 +9,9 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"moduleResolution": "node"
|
"moduleResolution": "node",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js"
|
||||||
},
|
},
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"display": "Recommended"
|
"display": "Recommended"
|
||||||
|
|||||||
30
vite.config.ts
Normal file
30
vite.config.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import solidPlugin from 'vite-plugin-solid'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solidPlugin()],
|
||||||
|
build: {
|
||||||
|
outDir: './static/bundled',
|
||||||
|
watch: {
|
||||||
|
include: 'assets/**'
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
input: './assets/ts/main.tsx',
|
||||||
|
output: {
|
||||||
|
format: 'es',
|
||||||
|
entryFileNames: 'js/[name].js',
|
||||||
|
chunkFileNames: 'js/[hash:6].js',
|
||||||
|
assetFileNames: '[ext]/[name].[ext]',
|
||||||
|
compact: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
passes: 3
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
comments: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user