├── .editorconfig
├── .firebaserc
├── .gcloudignore
├── .github
└── workflows
│ ├── main.yml
│ └── test.yml
├── .gitignore
├── CONTRIBUTE.md
├── LICENSE
├── VisBug
├── VisBug Extension
│ ├── Info.plist
│ ├── SafariWebExtensionHandler.swift
│ └── VisBug_Extension.entitlements
├── VisBug.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── VisBug.xcscheme
│ └── xcuserdata
│ │ └── argyle.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── VisBug
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── VisBug1x-1.png
│ │ ├── VisBug1x-2.png
│ │ ├── VisBug1x.png
│ │ ├── VisBug2x-1.png
│ │ ├── VisBug2x-4.png
│ │ └── VisBug2x.png
│ └── Contents.json
│ ├── Base.lproj
│ └── Main.storyboard
│ ├── Info.plist
│ ├── ViewController.swift
│ └── VisBug.entitlements
├── app.yaml
├── app
├── 404.html
├── assets
│ ├── arrow.png
│ ├── hero-1.svg
│ ├── hero-2.svg
│ ├── hero-3.svg
│ ├── hero-4.svg
│ └── visbug.png
├── components
│ ├── _variables.css
│ ├── _variables_dark.css
│ ├── _variables_light.css
│ ├── hotkey-map
│ │ ├── accessibility.element.js
│ │ ├── align.element.js
│ │ ├── base.element.css
│ │ ├── base.element.js
│ │ ├── base.element_dark.css
│ │ ├── base.element_light.css
│ │ ├── boxshadow.element.js
│ │ ├── font.element.js
│ │ ├── guides.element.js
│ │ ├── hotkeys.element.js
│ │ ├── hueshift.element.js
│ │ ├── inspector.element.js
│ │ ├── margin.element.js
│ │ ├── move.element.js
│ │ ├── padding.element.js
│ │ ├── position.element.js
│ │ ├── search.element.js
│ │ └── text.element.js
│ ├── index.js
│ ├── metatip
│ │ ├── ally.element.js
│ │ ├── metatip.element.css
│ │ ├── metatip.element.js
│ │ ├── metatip.element_dark.css
│ │ └── metatip.element_light.css
│ ├── selection
│ │ ├── box-model.element.css
│ │ ├── box-model.element.js
│ │ ├── corners.element.css
│ │ ├── corners.element.js
│ │ ├── distance.element.css
│ │ ├── distance.element.js
│ │ ├── gridlines.element.css
│ │ ├── gridlines.element.js
│ │ ├── grip.element.css
│ │ ├── grip.element.js
│ │ ├── handle.element.css
│ │ ├── handle.element.js
│ │ ├── handles.element.css
│ │ ├── handles.element.js
│ │ ├── hover.element.css
│ │ ├── hover.element.js
│ │ ├── label.element.css
│ │ ├── label.element.js
│ │ ├── offscreenLabel.element.css
│ │ ├── offscreenLabel.element.js
│ │ ├── overlay.element.css
│ │ └── overlay.element.js
│ ├── styles.store.js
│ └── vis-bug
│ │ ├── model.js
│ │ ├── vis-bug.css
│ │ ├── vis-bug.element.css
│ │ ├── vis-bug.element.js
│ │ ├── vis-bug.element_dark.css
│ │ ├── vis-bug.element_light.css
│ │ ├── vis-bug.icons.js
│ │ └── vis-bug.test.js
├── demo
│ ├── artboard.css
│ ├── card.css
│ ├── index.css
│ ├── layout.css
│ ├── mock-ad.css
│ ├── multi-select.css
│ ├── shapes.css
│ └── typography.css
├── extension.css
├── favicon.ico
├── features
│ ├── accessibility.js
│ ├── accessibility.test.js
│ ├── boxshadow.js
│ ├── boxshadow.test.js
│ ├── color.js
│ ├── flex.js
│ ├── flex.test.js
│ ├── font.js
│ ├── font.test.js
│ ├── guides.js
│ ├── guides.test.js
│ ├── hueshift.js
│ ├── imageswap.js
│ ├── index.js
│ ├── margin.js
│ ├── margin.test.js
│ ├── measurements.js
│ ├── metatip.js
│ ├── metatip.test.js
│ ├── move.js
│ ├── move.test.js
│ ├── padding.js
│ ├── padding.test.js
│ ├── position.js
│ ├── position.test.js
│ ├── screenshot.js
│ ├── search.js
│ ├── selectable.js
│ ├── selectable.test.js
│ ├── text.js
│ └── text.test.js
├── index.css
├── index.html
├── index.js
├── plugins
│ ├── _dynamic-registery.js
│ ├── _registry.js
│ ├── barrel-roll.js
│ ├── blank-page.js
│ ├── colorblind.js
│ ├── construct.debug.js
│ ├── construct.js
│ ├── ct-head-scan.js
│ ├── detect-overflows.js
│ ├── expand-text.js
│ ├── loop-through-widths.js
│ ├── no-mouse-days.js
│ ├── pesticide.js
│ ├── placeholdifier.js
│ ├── remove-css.js
│ ├── revenge.js
│ ├── shuffle.js
│ ├── skeleton.js
│ ├── tag-debugger.js
│ ├── tota11y.js
│ ├── wireframe.js
│ └── zindex.js
├── tuts
│ ├── accessibility.gif
│ ├── align.gif
│ ├── boxshadow.gif
│ ├── font.gif
│ ├── guides.gif
│ ├── hueshift.gif
│ ├── inspector.gif
│ ├── margin.gif
│ ├── move.gif
│ ├── padding.gif
│ ├── position.gif
│ ├── search.gif
│ └── text.gif
└── utilities
│ ├── accessibility.js
│ ├── colors.js
│ ├── common.js
│ ├── cross-browser.js
│ ├── design-properties.js
│ ├── index.js
│ ├── isFixed.js
│ ├── numbers.js
│ ├── scheme.js
│ ├── strings.js
│ ├── styles.js
│ └── window.js
├── assets
├── tuts_src
│ ├── a11y.gif
│ ├── edittext.gif
│ ├── flexbox.gif
│ ├── guides.gif
│ ├── hueshift.gif
│ ├── margin.gif
│ ├── metatip.gif
│ ├── move.gif
│ ├── padding.gif
│ ├── position.gif
│ ├── search.gif
│ ├── shadow.gif
│ └── typography.gif
├── visbug.png
├── visbug.sketch
└── visbug.svg
├── extension
├── build
│ └── .keep
├── contextmenu
│ ├── colormode.js
│ ├── colorscheme.js
│ └── launcher.js
├── icons
│ ├── visbug-dev.png
│ ├── visbug-original.png
│ └── visbug.png
├── manifest.json
├── toolbar
│ ├── eject.js
│ ├── inject.js
│ └── restore.js
└── visbug.js
├── firebase.json
├── package-lock.json
├── package.json
├── postcss.config.js
├── readme.md
├── rollup.config.mjs
└── tests
└── helpers.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [Makefile]
15 | indent_style = tab
16 |
17 | [{*.md,*.json}]
18 | max_line_length = null
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "visbug"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gcloudignore:
--------------------------------------------------------------------------------
1 | # This file specifies files that are *not* uploaded to Google Cloud Platform
2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of
3 | # "#!include" directives (which insert the entries of the given .gitignore-style
4 | # file at that point).
5 | #
6 | # For more information, run:
7 | # $ gcloud topic gcloudignore
8 | #
9 | .gcloudignore
10 | # If you would like to upload your .git directory, .gitignore file or files
11 | # from your .gitignore file, remove the corresponding line
12 | # below:
13 | .git
14 | .gitignore
15 |
16 | # Node.js dependencies:
17 | node_modules/
18 | extension/
19 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI/CD
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 |
13 | - name: Use Node.js 16.x
14 | uses: actions/setup-node@v1
15 | with:
16 | node-version: 16.x
17 |
18 | - name: Git Init
19 | run: |
20 | git switch -c main
21 | git config --global user.email "argyle@google.com"
22 | git config --global user.name "Adam Argyle"
23 |
24 | - name: Test & Build
25 | run: |
26 | npm install
27 | npm run extension:release
28 |
29 | - name: Git Push
30 | uses: ad-m/github-push-action@master
31 | continue-on-error: true
32 | with:
33 | tags: true
34 | branch: 'main'
35 | github_token: ${{ secrets.GITHUB_TOKEN }}
36 |
37 | - name: Save Build As Artifact
38 | uses: actions/upload-artifact@v4
39 | continue-on-error: true
40 | with:
41 | name: VisBug
42 | path: extension/build/visbug.zip
43 |
44 | - name: Publish Chrome Extension
45 | uses: trmcnvn/chrome-addon@v2
46 | continue-on-error: true
47 | with:
48 | extension: cdockenadnadldjbbgcallicgledbeoc
49 | zip: extension/build/visbug.zip
50 | client-id: ${{ secrets.CHROME_CLIENT_ID }}
51 | client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
52 | refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
53 |
54 | - name: Firebase Deploy
55 | uses: pizzafox/firebase-action@1.0.7
56 | env:
57 | PROJECT_ID: "visbug"
58 | FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
59 | with:
60 | args: deploy
61 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v1
13 |
14 | - name: Use Node.js 16.x
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: 16.x
18 |
19 | - name: Test & Build
20 | run: |
21 | npm ci
22 | npm run extension:build
23 | npm run test:ci
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | node_modules/
4 | app/bundle.js
5 | app/bundle.min.js
6 | app/bundle.css
7 | extension/toolbar/bundle.min.js
8 | extension/toolbar/bundle.css
9 | extension/build
10 | extension/tuts
11 | VisBug/VisBug.xcodeproj/project.xcworkspace/xcuserdata/argyle.xcuserdatad/UserInterfaceState.xcuserstate
12 |
--------------------------------------------------------------------------------
/CONTRIBUTE.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
--------------------------------------------------------------------------------
/VisBug/VisBug Extension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | VisBug Extension
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSExtension
26 |
27 | NSExtensionPointIdentifier
28 | com.apple.Safari.web-extension
29 | NSExtensionPrincipalClass
30 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/VisBug/VisBug Extension/SafariWebExtensionHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariWebExtensionHandler.swift
3 | // VisBug Extension
4 | //
5 | // Created by Adam Argyle on 11/2/20.
6 | //
7 |
8 | import SafariServices
9 | import os.log
10 |
11 | let SFExtensionMessageKey = "message"
12 |
13 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
14 |
15 | func beginRequest(with context: NSExtensionContext) {
16 | let item = context.inputItems[0] as! NSExtensionItem
17 | let message = item.userInfo?[SFExtensionMessageKey]
18 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
19 |
20 | let response = NSExtensionItem()
21 | response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]
22 |
23 | context.completeRequest(returningItems: [response], completionHandler: nil)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/VisBug/VisBug Extension/VisBug_Extension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/VisBug/VisBug.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/VisBug/VisBug.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/VisBug/VisBug.xcodeproj/xcshareddata/xcschemes/VisBug.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/VisBug/VisBug.xcodeproj/xcuserdata/argyle.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/VisBug/VisBug.xcodeproj/xcuserdata/argyle.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | VisBug Extension.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | VisBug.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 8EBF957C2550B10A007B8136
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/VisBug/VisBug/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // VisBug
4 | //
5 | // Created by Adam Argyle on 11/3/20.
6 | //
7 |
8 | import Cocoa
9 |
10 | @NSApplicationMain
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ aNotification: Notification) {
14 | // Insert code here to initialize your application
15 | }
16 |
17 | func applicationWillTerminate(_ aNotification: Notification) {
18 | // Insert code here to tear down your application
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "0.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "filename" : "VisBug1x-2.png",
25 | "idiom" : "mac",
26 | "scale" : "1x",
27 | "size" : "128x128"
28 | },
29 | {
30 | "filename" : "VisBug2x-4.png",
31 | "idiom" : "mac",
32 | "scale" : "2x",
33 | "size" : "128x128"
34 | },
35 | {
36 | "filename" : "VisBug1x-1.png",
37 | "idiom" : "mac",
38 | "scale" : "1x",
39 | "size" : "256x256"
40 | },
41 | {
42 | "filename" : "VisBug2x-1.png",
43 | "idiom" : "mac",
44 | "scale" : "2x",
45 | "size" : "256x256"
46 | },
47 | {
48 | "filename" : "VisBug1x.png",
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "filename" : "VisBug2x.png",
55 | "idiom" : "mac",
56 | "scale" : "2x",
57 | "size" : "512x512"
58 | }
59 | ],
60 | "info" : {
61 | "author" : "xcode",
62 | "version" : 1
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug1x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug1x-1.png
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug1x-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug1x-2.png
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug1x.png
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug2x-1.png
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug2x-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug2x-4.png
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/VisBug/VisBug/Assets.xcassets/AppIcon.appiconset/VisBug2x.png
--------------------------------------------------------------------------------
/VisBug/VisBug/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/VisBug/VisBug/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | LSApplicationCategoryType
24 | public.app-category.developer-tools
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/VisBug/VisBug/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // VisBug
4 | //
5 | // Created by Adam Argyle on 11/2/20.
6 | //
7 |
8 | import Cocoa
9 | import SafariServices.SFSafariApplication
10 | import SafariServices.SFSafariExtensionManager
11 |
12 | let appName = "VisBug"
13 | let extensionBundleIdentifier = "argyleink.VisBug.Extension"
14 |
15 | class ViewController: NSViewController {
16 |
17 | @IBOutlet var appNameLabel: NSTextField!
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | self.appNameLabel.stringValue = appName
22 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
23 | guard let state = state, error == nil else {
24 | // Insert code to inform the user that something went wrong.
25 | return
26 | }
27 |
28 | DispatchQueue.main.async {
29 | if (state.isEnabled) {
30 | self.appNameLabel.stringValue = "\(appName)'s extension is currently on."
31 | } else {
32 | self.appNameLabel.stringValue = "\(appName)'s extension is currently off. You can turn it on in Safari Extensions preferences."
33 | }
34 | }
35 | }
36 | }
37 |
38 | @IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) {
39 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
40 | guard error == nil else {
41 | // Insert code to inform the user that something went wrong.
42 | return
43 | }
44 |
45 | DispatchQueue.main.async {
46 | NSApplication.shared.terminate(nil)
47 | }
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/VisBug/VisBug/VisBug.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app.yaml:
--------------------------------------------------------------------------------
1 | runtime: python27
2 | api_version: 1
3 | threadsafe: true
4 |
5 | handlers:
6 | - url: /(.*\.css)
7 | mime_type: text/css
8 | static_files: app/\1
9 | upload: app/(.*\.css)
10 |
11 | - url: /(.*\.js)
12 | mime_type: text/javascript
13 | static_files: app/\1
14 | upload: app/(.*\.js)
15 |
16 | - url: /(.*\.(bmp|gif|ico|jpeg|jpg|png))
17 | static_files: app/\1
18 | upload: app/(.*\.(bmp|gif|ico|jpeg|jpg|png))
19 |
20 | - url: /(.*\.(svg|svgz))
21 | mime_type: image/svg+xml
22 | static_files: app/\1
23 | upload: app/(.*\.(svg|svgz))
24 |
25 | - url: /(.+)/
26 | static_files: app/\1/index.html
27 | upload: app/(.*)/index.html
28 |
29 | - url: /(.+)
30 | static_files: app/\1/index.html
31 | upload: app/(.*)/index.html
32 |
33 | - url: /
34 | static_files: app/index.html
35 | upload: app/index.html
36 |
--------------------------------------------------------------------------------
/app/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Page Not Found
7 |
8 |
23 |
24 |
25 |
26 |
404
27 |
Page Not Found
28 |
The specified file was not found on this website. Please check the URL for mistakes and try again.
29 |
Why am I seeing this?
30 |
This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html
file in your project's configured public
directory.
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/assets/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/app/assets/arrow.png
--------------------------------------------------------------------------------
/app/assets/visbug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChromeLabs/ProjectVisBug/382ecff58dec46b5e1c21e9d1801c4a58afda599/app/assets/visbug.png
--------------------------------------------------------------------------------
/app/components/_variables.css:
--------------------------------------------------------------------------------
1 | @import "open-props/shadows.shadow.min.css";
2 | @import "open-props/borders.shadow.min.css";
3 | @import "open-props/gray.shadow.min.css";
4 | @import "open-props/gray-hsl.shadow.min.css";
5 |
6 | :host {
7 | --theme-bg: white;
8 | --theme-bd: hsl(0 0% 100% / var(--theme-bd-opacity));
9 | --theme-bd-2: hsl(0 0% 100% / var(--theme-bd-2-opacity));
10 | --theme-bd-opacity: 1;
11 | --theme-bd-2-opacity: 1;
12 | --theme-color: hotpink;
13 | --theme-blue: hsl(188 90% 45%);
14 | --theme-purple: hsl(267 100% 58%);
15 |
16 | --neon-pink: color(display-p3 1 0 1);
17 | --neon-purple: color(display-p3 .5 0 1);
18 | --neon-lime: color(display-p3 0.25 1 0);
19 | --neon-cyan: color(display-p3 0 1 1);
20 |
21 | --theme-text_color: var(--gray-11);
22 | --theme-icon_color: var(--gray-9);
23 | --theme-icon_hover-bg: white;
24 | --theme-icon_active-bg: white;
25 |
26 | --layer-top: 2147483647;
27 | --layer-1: 2147483646;
28 | --layer-2: 2147483645;
29 | --layer-3: 2147483644;
30 | --layer-4: 2147483643;
31 | --layer-5: 2147483642;
32 |
33 | --text-shadow: 0 1px hsl(0 0% 0% / 40%);
34 |
35 | @media (-webkit-min-device-pixel-ratio: 2) {
36 | --text-shadow: 0 .5px hsl(0 0% 0% / 50%);
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | --theme-bg: var(--gray-10);
41 | --theme-bd: hsl(var(--gray-10-hsl) / var(--theme-bd-opacity));
42 | --theme-bd-2: hsl(var(--gray-10-hsl) / var(--theme-bd-2-opacity));
43 | --theme-color: hsl(330deg 65% 75%);
44 | --theme-text_color: var(--gray-0);
45 | --theme-icon_color: var(--gray-2);
46 | --theme-icon_hover-bg: var(--gray-8);
47 | --theme-icon_active-bg: var(--gray-8);
48 | }
49 |
50 | @supports (backdrop-filter: blur(5px)) {
51 | --theme-bd-opacity: .8;
52 | --theme-bd-2-opacity: .9;
53 | }
54 |
55 | @supports (-webkit-backdrop-filter: blur(5px)) {
56 | --theme-bd-opacity: .8;
57 | --theme-bd-2-opacity: .9;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/components/_variables_dark.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --theme-bg: hsl(0 0% 10%);
3 | --theme-bd: hsl(0 0% 10% / var(--theme-bd-opacity));
4 | --theme-bd-2: hsl(0 0% 10% / var(--theme-bd-2-opacity));
5 | --theme-color: hsl(330deg 65% 75%);
6 | --theme-text_color: hsl(0 0% 90%);
7 | --theme-icon_color: hsl(0 0% 80%);
8 | --theme-icon_hover-bg: hsl(0 0% 15%);
9 | --theme-icon_active-bg: hsl(0 0% 20%);
10 | }
11 |
--------------------------------------------------------------------------------
/app/components/_variables_light.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --theme-bg: hsl(0 0% 100%);
3 | --theme-bd: hsl(0 0% 100% / var(--theme-bd-opacity));
4 | --theme-bd-2: hsl(0 0% 100% / var(--theme-bd-2-opacity));
5 | --theme-color: hotpink;
6 | --theme-text_color: hsl(0 0% 10%);
7 | --theme-icon_color: hsl(0 0% 20%);
8 | --theme-icon_hover-bg: hsl(0 0% 95%);
9 | --theme-icon_active-bg: hsl(0 0% 90%);
10 | }
11 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/accessibility.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { accessibility as icon } from '../vis-bug/vis-bug.icons'
3 | import { altKey } from '../../utilities';
4 |
5 | export class AccessibilityHotkeys extends HotkeyMap {
6 | constructor() {
7 | super()
8 |
9 | this._hotkey = 'p'
10 | this._usedkeys = [altKey]
11 | this.tool = 'accessibility'
12 | }
13 |
14 | show() {
15 | this.$shadow.host.style.display = 'flex'
16 | }
17 |
18 | render() {
19 | return `
20 |
21 |
22 |
23 | ${icon}
24 | ${this._tool} Tool
25 |
26 |
27 |
28 | coming soon
29 |
30 |
31 | `
32 | }
33 | }
34 |
35 | customElements.define('hotkeys-accessibility', AccessibilityHotkeys)
36 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/align.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { metaKey } from '../../utilities';
3 |
4 | const h_alignOptions = ['left','center','right']
5 | const v_alignOptions = ['top','center','bottom']
6 | const distOptions = ['evenly','normal','between']
7 |
8 | export class AlignHotkeys extends HotkeyMap {
9 | constructor() {
10 | super()
11 |
12 | this._hotkey = 'a'
13 | this._usedkeys = [metaKey,'shift']
14 |
15 | this._htool = 0
16 | this._vtool = 0
17 | this._dtool = 1
18 |
19 | this._side = 'top left'
20 | this._direction = 'row'
21 | this._distribution = distOptions[this._dtool]
22 | this._wrap = 'no wrap'
23 |
24 | this.tool = 'align'
25 | }
26 |
27 | createCommand({e:{code}, hotkeys}) {
28 | let amount = this._distribution
29 | , negative_modifier = this._direction
30 | , side = this._side
31 | , negative = this._wrap
32 |
33 | if (hotkeys[metaKey] && hotkeys.shift) {
34 | if (code === 'ArrowUp')
35 | negative = 'no wrap'
36 | else if (code === 'ArrowDown')
37 | negative = 'wrap'
38 | else if (code === 'ArrowLeft')
39 | negative_modifier = `${negative_modifier}-reverse`
40 | }
41 | else if (hotkeys[metaKey] && (code === 'ArrowRight' || code === 'ArrowDown')) {
42 | negative_modifier = code === 'ArrowDown'
43 | ? 'column'
44 | : 'row'
45 | this._direction = negative_modifier
46 | }
47 | else {
48 | if (code === 'ArrowUp') side = this.clamp(v_alignOptions, '_vtool')
49 | else if (code === 'ArrowDown') side = this.clamp(v_alignOptions, '_vtool', true)
50 | else side = v_alignOptions[this._vtool]
51 |
52 | if (code === 'ArrowLeft') side += ' ' + this.clamp(h_alignOptions, '_htool')
53 | else if (code === 'ArrowRight') side += ' ' + this.clamp(h_alignOptions, '_htool', true)
54 | else side += ' ' + h_alignOptions[this._htool]
55 |
56 | this._side = side
57 |
58 | if (hotkeys.shift && (code === 'ArrowRight' || code === 'ArrowLeft')) {
59 | amount = this.clamp(distOptions, '_dtool', code === 'ArrowRight')
60 | this._distribution = amount
61 | }
62 | }
63 |
64 | return {
65 | negative, negative_modifier, amount, side,
66 | }
67 | }
68 |
69 | displayCommand({side, amount, negative, negative_modifier}) {
70 | if (amount == 1) amount = this._distribution
71 | if (negative_modifier == ' to ') negative_modifier = this._direction
72 |
73 | return `
74 | ${this._tool}
75 | as
76 | ${negative_modifier}:
77 | ${side}
78 | , distributed
79 | ${amount},
80 | with
81 | ${negative}
82 | `
83 | }
84 |
85 | clamp(range, tool, increment = false) {
86 | if (increment) {
87 | if (this[tool] < range.length - 1)
88 | this[tool] = this[tool] + 1
89 | }
90 | else if (this[tool] > 0)
91 | this[tool] = this[tool] - 1
92 |
93 | return range[this[tool]]
94 | }
95 | }
96 |
97 | customElements.define('hotkeys-align', AlignHotkeys)
98 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/base.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host {
4 | display: none;
5 | position: fixed;
6 | top: 0;
7 | left: 0;
8 | z-index: var(--layer-top);
9 | align-items: center;
10 | justify-content: center;
11 | width: 100vw;
12 | height: 100vh;
13 | background: var(--theme-bd-2);
14 | backdrop-filter: blur(5px);
15 | font-size: 16px;
16 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
17 | cursor: initial;
18 |
19 | --light-grey: hsl(0 0% 90%);
20 | --grey: hsl(0 0% 60%);
21 | --dark-grey: hsl(0 0% 40%);
22 |
23 | @media (prefers-color-scheme: dark) {
24 | --light-grey: hsl(0 0% 20%);
25 | --grey: hsl(0 0% 60%);
26 | --dark-grey: hsl(0 0% 80%);
27 | }
28 | }
29 |
30 | :host [command] {
31 | padding: 1em;
32 | text-align: center;
33 | font-size: 3vw;
34 | font-weight: lighter;
35 | letter-spacing: 0.1em;
36 | color: var(--dark-grey);
37 |
38 | & > [light] {
39 | color: var(--grey);
40 | }
41 |
42 | & > [tool] {
43 | text-decoration: underline;
44 | text-decoration-color: var(--theme-color);
45 | }
46 |
47 | & > :matches([negative],[side],[amount]) {
48 | font-weight: normal;
49 | }
50 | }
51 |
52 | :host [card] {
53 | padding: 1em;
54 | background: var(--theme-bg);
55 | border-radius: 0.5em;
56 | color: var(--dark-grey);
57 | display: flex;
58 | justify-content: space-evenly;
59 |
60 | & > div:not([keyboard]) {
61 | display: flex;
62 | align-items: flex-end;
63 | margin-left: 1em;
64 | }
65 | }
66 |
67 | :host [tool-icon] {
68 | position: absolute;
69 | top: 1em;
70 | left: 0;
71 | width: 100%;
72 | padding: 0 1rem;
73 | display: flex;
74 | justify-content: center;
75 |
76 | & > span {
77 | color: var(--dark-grey);
78 | display: grid;
79 | grid-template-columns: 5vmax auto;
80 | grid-gap: 0.5em;
81 | align-items: center;
82 | text-transform: capitalize;
83 | font-size: 4vmax;
84 | font-weight: lighter;
85 | }
86 |
87 | & svg {
88 | width: 100%;
89 | fill: var(--theme-color);
90 | }
91 | }
92 |
93 | :host section {
94 | display: flex;
95 | justify-content: center;
96 | }
97 |
98 | :host section > span,
99 | :host [arrows] > span {
100 | border: 1px solid transparent;
101 | border-radius: 0.5em;
102 | display: inline-flex;
103 | align-items: center;
104 | justify-content: center;
105 | margin: 2px;
106 | padding: 1.5vw;
107 | font-size: 0.75em;
108 | white-space: nowrap;
109 | }
110 |
111 | :host section > span:not([pressed="true"]),
112 | :host [arrows] > span:not([pressed="true"]) {
113 | border: 1px solid var(--light-grey);
114 |
115 | &:hover {
116 | border-color: var(--grey);
117 | }
118 | }
119 |
120 | :host span[pressed="true"] {
121 | background: var(--theme-color);
122 | color: var(--theme-bg);
123 | }
124 |
125 | :host span:not([pressed="true"]):matches([used]) {
126 | background: var(--light-grey);
127 | cursor: pointer;
128 | }
129 |
130 | :host span[hotkey] {
131 | color: var(--theme-color);
132 | font-weight: bold;
133 | cursor: pointer;
134 | }
135 |
136 | :host section > span[hotkey]:not([pressed="true"]) {
137 | border-color: var(--theme-color);
138 | }
139 |
140 | :host [arrows] {
141 | display: grid;
142 | grid-template-columns: 1fr 1fr 1fr;
143 | grid-template-rows: 1fr 1fr;
144 |
145 | & > span:nth-child(1) {
146 | grid-row: 1;
147 | grid-column: 2;
148 | }
149 |
150 | & > span:nth-child(2) {
151 | grid-row: 2;
152 | grid-column: 2;
153 | }
154 |
155 | & > span:nth-child(3) {
156 | grid-row: 2;
157 | grid-column: 1;
158 | }
159 |
160 | & > span:nth-child(4) {
161 | grid-row: 2;
162 | grid-column: 3;
163 | }
164 | }
165 |
166 | :host [caps] > span:nth-child(1),
167 | :host [shift] > span:nth-child(1) { justify-content: flex-start; }
168 | :host [shift] > span:nth-child(12) { justify-content: flex-end; }
--------------------------------------------------------------------------------
/app/components/hotkey-map/base.element_dark.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --light-grey: hsl(0 0% 20%);
3 | --grey: hsl(0 0% 60%);
4 | --dark-grey: hsl(0 0% 80%);
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/base.element_light.css:
--------------------------------------------------------------------------------
1 | :host {
2 | --light-grey: hsl(0 0% 90%);
3 | --grey: hsl(0 0% 60%);
4 | --dark-grey: hsl(0 0% 40%);
5 | }
6 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/boxshadow.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { boxshadow as icon } from '../vis-bug/vis-bug.icons'
3 | import { metaKey } from '../../utilities';
4 |
5 | export class BoxshadowHotkeys extends HotkeyMap {
6 | constructor() {
7 | super()
8 |
9 | this._hotkey = 'd'
10 | this._usedkeys = ['shift',metaKey]
11 | this.tool = 'boxshadow'
12 | }
13 |
14 | show() {
15 | this.$shadow.host.style.display = 'flex'
16 | }
17 |
18 | render() {
19 | return `
20 |
21 |
22 |
23 | ${icon}
24 | ${this._tool} Tool
25 |
26 |
27 |
28 | coming soon
29 |
30 |
31 | `
32 | }
33 | }
34 |
35 | customElements.define('hotkeys-boxshadow', BoxshadowHotkeys)
36 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/font.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { metaKey, altKey } from '../../utilities';
3 |
4 | export class FontHotkeys extends HotkeyMap {
5 | constructor() {
6 | super()
7 |
8 | this._hotkey = 'f'
9 | this._usedkeys = ['shift',metaKey]
10 | this.tool = 'font'
11 | }
12 |
13 | createCommand({e:{code}, hotkeys}) {
14 | let amount = hotkeys.shift ? 10 : 1
15 | let negative = '[increase/decrease]'
16 | let negative_modifier = 'by'
17 | let side = '[arrow key]'
18 |
19 | // kerning
20 | if (hotkeys.shift && (code === 'ArrowLeft' || code === 'ArrowRight')) {
21 | side = 'kerning'
22 | amount = '1px'
23 |
24 | if (code === 'ArrowLeft')
25 | negative = 'decrease'
26 | if (code === 'ArrowRight')
27 | negative = 'increase'
28 | }
29 | // leading
30 | else if (hotkeys.shift && (code === 'ArrowUp' || code === 'ArrowDown')) {
31 | side = 'leading'
32 | amount = '1px'
33 |
34 | if (code === 'ArrowUp')
35 | negative = 'increase'
36 | if (code === 'ArrowDown')
37 | negative = 'decrease'
38 | }
39 | // font weight
40 | else if (hotkeys[metaKey] && (code === 'ArrowUp' || code === 'ArrowDown')) {
41 | side = 'font weight'
42 | amount = ''
43 | negative_modifier = ''
44 |
45 | if (code === 'ArrowUp')
46 | negative = 'increase'
47 | if (code === 'ArrowDown')
48 | negative = 'decrease'
49 | }
50 | // font size
51 | else if (code === 'ArrowUp' || code === 'ArrowDown') {
52 | side = 'font size'
53 | amount = '1px'
54 |
55 | if (code === 'ArrowUp')
56 | negative = 'increase'
57 | if (code === 'ArrowDown')
58 | negative = 'decrease'
59 | }
60 | // text alignment
61 | else if (code === 'ArrowRight' || code === 'ArrowLeft') {
62 | side = 'text alignment'
63 | amount = ''
64 | negative = 'adjust'
65 | negative_modifier = ''
66 | }
67 |
68 | return {
69 | negative, negative_modifier, amount, side,
70 | }
71 | }
72 |
73 | displayCommand({negative, negative_modifier, side, amount}) {
74 | if (negative === `±[${altKey}] `)
75 | negative = '[increase/decrease]'
76 | if (negative_modifier === ' to ')
77 | negative_modifier = ' by '
78 |
79 | return `
80 | ${negative}
81 | ${side}
82 | ${negative_modifier}
83 | ${amount}
84 | `
85 | }
86 | }
87 |
88 | customElements.define('hotkeys-font', FontHotkeys)
89 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/guides.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { guides as icon } from '../vis-bug/vis-bug.icons'
3 |
4 | export class GuidesHotkeys extends HotkeyMap {
5 | constructor() {
6 | super()
7 |
8 | this._hotkey = 'g'
9 | this._usedkeys = []
10 | this.tool = 'guides'
11 | }
12 |
13 | show() {
14 | this.$shadow.host.style.display = 'flex'
15 | }
16 |
17 | render() {
18 | return `
19 |
20 |
21 |
22 | ${icon}
23 | ${this._tool} Tool
24 |
25 |
26 |
27 | coming soon
28 |
29 |
30 | `
31 | }
32 | }
33 |
34 | customElements.define('hotkeys-guides', GuidesHotkeys)
35 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/hotkeys.element.js:
--------------------------------------------------------------------------------
1 | import $ from 'blingblingjs'
2 | import hotkeys from 'hotkeys-js'
3 |
4 | import { GuidesHotkeys } from './guides.element'
5 | import { InspectorHotkeys } from './inspector.element'
6 | import { AccessibilityHotkeys } from './accessibility.element'
7 | import { MoveHotkeys } from './move.element'
8 | import { MarginHotkeys } from './margin.element'
9 | import { PaddingHotkeys } from './padding.element'
10 | import { AlignHotkeys } from './align.element'
11 | import { HueshiftHotkeys } from './hueshift.element'
12 | import { BoxshadowHotkeys } from './boxshadow.element'
13 | import { PositionHotkeys } from './position.element'
14 | import { FontHotkeys } from './font.element'
15 | import { TextHotkeys } from './text.element'
16 | import { SearchHotkeys } from './search.element'
17 |
18 | export class Hotkeys extends HTMLElement {
19 |
20 | constructor() {
21 | super()
22 |
23 | this.tool_map = {
24 | guides: document.createElement('hotkeys-guides'),
25 | inspector: document.createElement('hotkeys-inspector'),
26 | accessibility: document.createElement('hotkeys-accessibility'),
27 | move: document.createElement('hotkeys-move'),
28 | margin: document.createElement('hotkeys-margin'),
29 | padding: document.createElement('hotkeys-padding'),
30 | align: document.createElement('hotkeys-align'),
31 | hueshift: document.createElement('hotkeys-hueshift'),
32 | boxshadow: document.createElement('hotkeys-boxshadow'),
33 | position: document.createElement('hotkeys-position'),
34 | font: document.createElement('hotkeys-font'),
35 | text: document.createElement('hotkeys-text'),
36 | search: document.createElement('hotkeys-search'),
37 | }
38 |
39 | Object.values(this.tool_map).forEach(tool =>
40 | this.appendChild(tool))
41 | }
42 |
43 | connectedCallback() {
44 | hotkeys('shift+/', e =>
45 | this.cur_tool
46 | ? this.hideTool()
47 | : this.showTool())
48 |
49 | hotkeys('esc', e => this.hideTool())
50 | }
51 |
52 | disconnectedCallback() {}
53 |
54 | hideTool() {
55 | if (!this.cur_tool) return
56 | this.cur_tool.hide()
57 | this.cur_tool = null
58 | }
59 |
60 | showTool() {
61 | this.cur_tool = this.tool_map[
62 | $('vis-bug')[0].activeTool]
63 | this.cur_tool.show()
64 | }
65 | }
66 |
67 | customElements.define('visbug-hotkeys', Hotkeys)
68 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/hueshift.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { hueshift as icon } from '../vis-bug/vis-bug.icons'
3 | import { metaKey, altKey } from '../../utilities';
4 |
5 | export class HueshiftHotkeys extends HotkeyMap {
6 | constructor() {
7 | super()
8 |
9 | this._hotkey = 'h'
10 | this._usedkeys = ['shift',metaKey]
11 | this.tool = 'hueshift'
12 | }
13 |
14 | createCommand({e:{code}, hotkeys}) {
15 | let amount = hotkeys.shift ? 10 : 1
16 | let negative = '[increase/decrease]'
17 | let negative_modifier = 'by'
18 | let side = '[arrow key]'
19 |
20 | // saturation
21 | if (hotkeys[metaKey]) {
22 | side ='hue'
23 |
24 | if (code === 'ArrowDown')
25 | negative = 'decrease'
26 | if (code === 'ArrowUp')
27 | negative = 'increase'
28 | }
29 | else if (code === 'ArrowLeft' || code === 'ArrowRight') {
30 | side = 'saturation'
31 |
32 | if (code === 'ArrowLeft')
33 | negative = 'decrease'
34 | if (code === 'ArrowRight')
35 | negative = 'increase'
36 | }
37 | // lightness
38 | else if (code === 'ArrowUp' || code === 'ArrowDown') {
39 | side = 'lightness'
40 |
41 | if (code === 'ArrowDown')
42 | negative = 'decrease'
43 | if (code === 'ArrowUp')
44 | negative = 'increase'
45 | }
46 |
47 | return {
48 | negative, negative_modifier, amount, side,
49 | }
50 | }
51 |
52 | displayCommand({negative, negative_modifier, side, amount}) {
53 | if (negative === `±[${altKey}] `)
54 | negative = '[increase/decrease]'
55 | if (negative_modifier === ' to ')
56 | negative_modifier = ' by '
57 |
58 | return `
59 | ${negative}
60 | ${side}
61 | ${negative_modifier}
62 | ${amount}
63 | `
64 | }
65 | }
66 |
67 | customElements.define('hotkeys-hueshift', HueshiftHotkeys)
68 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/inspector.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { inspector as icon } from '../vis-bug/vis-bug.icons'
3 | import { altKey } from '../../utilities';
4 |
5 | export class InspectorHotkeys extends HotkeyMap {
6 | constructor() {
7 | super()
8 |
9 | this._hotkey = 'i'
10 | this._usedkeys = [altKey]
11 | this.tool = 'inspector'
12 | }
13 |
14 | show() {
15 | this.$shadow.host.style.display = 'flex'
16 | }
17 |
18 | render() {
19 | return `
20 |
21 |
22 |
23 | ${icon}
24 | ${this._tool} Tool
25 |
26 |
27 |
28 | coming soon
29 |
30 |
31 | `
32 | }
33 | }
34 |
35 | customElements.define('hotkeys-inspector', InspectorHotkeys)
36 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/margin.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { metaKey, altKey } from '../../utilities/'
3 |
4 | export class MarginHotkeys extends HotkeyMap {
5 | constructor() {
6 | super()
7 |
8 | this._hotkey = 'm'
9 | this._usedkeys = ['shift',metaKey,altKey]
10 |
11 | this.tool = 'margin'
12 | }
13 | }
14 |
15 | customElements.define('hotkeys-margin', MarginHotkeys)
16 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/move.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 |
3 | export class MoveHotkeys extends HotkeyMap {
4 | constructor() {
5 | super()
6 |
7 | this._hotkey = 'v'
8 | this.tool = 'move'
9 | }
10 |
11 | createCommand({e:{code}, hotkeys}) {
12 | let amount, negative, negative_modifier
13 |
14 | let side = '[arrow key]'
15 | if (code === 'ArrowUp') side = 'up & out of div'
16 | if (code === 'ArrowDown') side = 'down & into next sibling / out & under div'
17 | if (code === 'ArrowLeft') side = 'towards the front/top of the stack'
18 | if (code === 'ArrowRight') side = 'towards the back/bottom of the stack'
19 |
20 | return {
21 | negative, negative_modifier, amount, side,
22 | }
23 | }
24 |
25 | displayCommand({side}) {
26 | return `
27 | ${this._tool}
28 | ${side}
29 | `
30 | }
31 | }
32 |
33 | customElements.define('hotkeys-move', MoveHotkeys)
--------------------------------------------------------------------------------
/app/components/hotkey-map/padding.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { metaKey, altKey } from '../../utilities/'
3 |
4 | export class PaddingHotkeys extends HotkeyMap {
5 | constructor() {
6 | super()
7 |
8 | this._hotkey = 'p'
9 | this._usedkeys = ['shift',metaKey,altKey]
10 |
11 | this.tool = 'padding'
12 | }
13 | }
14 |
15 | customElements.define('hotkeys-padding', PaddingHotkeys)
16 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/position.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { position as icon } from '../vis-bug/vis-bug.icons'
3 | import { altKey } from '../../utilities';
4 |
5 | export class PositionHotkeys extends HotkeyMap {
6 | constructor() {
7 | super()
8 |
9 | this._hotkey = 'l'
10 | this._usedkeys = ['shift',altKey]
11 | this.tool = 'position'
12 | }
13 |
14 | show() {
15 | this.$shadow.host.style.display = 'flex'
16 | }
17 |
18 | render() {
19 | return `
20 |
21 |
22 |
23 | ${icon}
24 | ${this._tool} Tool
25 |
26 |
27 |
28 | coming soon
29 |
30 |
31 | `
32 | }
33 | }
34 |
35 | customElements.define('hotkeys-position', PositionHotkeys)
36 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/search.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { search as icon } from '../vis-bug/vis-bug.icons'
3 |
4 | export class SearchHotkeys extends HotkeyMap {
5 | constructor() {
6 | super()
7 |
8 | this._hotkey = 's'
9 | this._usedkeys = []
10 | this.tool = 'search'
11 | }
12 |
13 | show() {
14 | this.$shadow.host.style.display = 'flex'
15 | }
16 |
17 | render() {
18 | return `
19 |
20 |
21 |
22 | ${icon}
23 | ${this._tool} Tool
24 |
25 |
26 |
27 | coming soon
28 |
29 |
30 | `
31 | }
32 | }
33 |
34 | customElements.define('hotkeys-search', SearchHotkeys)
35 |
--------------------------------------------------------------------------------
/app/components/hotkey-map/text.element.js:
--------------------------------------------------------------------------------
1 | import { HotkeyMap } from './base.element'
2 | import { text as icon } from '../vis-bug/vis-bug.icons'
3 |
4 | export class TextHotkeys extends HotkeyMap {
5 | constructor() {
6 | super()
7 |
8 | this._hotkey = 'e'
9 | this._usedkeys = []
10 | this.tool = 'text'
11 | }
12 |
13 | show() {
14 | this.$shadow.host.style.display = 'flex'
15 | }
16 |
17 | render() {
18 | return `
19 |
20 |
21 |
22 | ${icon}
23 | ${this._tool} Tool
24 |
25 |
26 |
27 | coming soon
28 |
29 |
30 | `
31 | }
32 | }
33 |
34 | customElements.define('hotkeys-text', TextHotkeys)
35 |
--------------------------------------------------------------------------------
/app/components/index.js:
--------------------------------------------------------------------------------
1 | export { Handles } from './selection/handles.element'
2 | export { Handle } from './selection/handle.element'
3 | export { Hover } from './selection/hover.element'
4 | export { Label } from './selection/label.element'
5 | export { Gridlines } from './selection/gridlines.element'
6 | export { Distance } from './selection/distance.element'
7 | export { Overlay } from './selection/overlay.element'
8 | export { BoxModel } from './selection/box-model.element'
9 | export { Corners } from './selection/corners.element'
10 | export { Grip } from './selection/grip.element'
11 |
12 | export { Metatip } from './metatip/metatip.element'
13 | export { Ally } from './metatip/ally.element'
14 |
15 | export { Hotkeys } from './hotkey-map/hotkeys.element'
16 |
--------------------------------------------------------------------------------
/app/components/metatip/ally.element.js:
--------------------------------------------------------------------------------
1 | import $ from 'blingblingjs'
2 | import { Metatip } from './metatip.element.js'
3 | import { preferredNotation } from '../../features/color.js'
4 | import { draggable } from '../../features/'
5 | import { getStyle, getComputedBackgroundColor } from '../../utilities'
6 | import { contrast_color } from '../../utilities'
7 |
8 | export class Ally extends Metatip {
9 | constructor() {
10 | super()
11 | this.copyColorSwatch = this.copyColorSwatch.bind(this)
12 | }
13 |
14 | async copyToClipboard(text) {
15 | const {state} = await navigator.permissions.query({name:'clipboard-write'})
16 |
17 | if (state === 'granted')
18 | navigator.clipboard.writeText(text)
19 | }
20 |
21 | copyColorSwatch(event) {
22 | this.copyToClipboard(event.currentTarget.querySelector('span').innerText.trim())
23 | }
24 |
25 | observe() {
26 | $('[color-swatch]', this.$shadow).on('click', this.copyColorSwatch)
27 |
28 | draggable({
29 | el: this,
30 | surface: this.$shadow.querySelector('header'),
31 | cursor: 'grab',
32 | })
33 | }
34 |
35 | unobserve() {
36 | $('[color-swatch]', this.$shadow).off('click', this.copyColorSwatch)
37 | }
38 |
39 | render({el, ally_attributes, contrast_results}) {
40 | const colormode = $('vis-bug').attr('color-mode')
41 |
42 | const foreground = el instanceof SVGElement
43 | ? (getStyle(el, 'fill') || getStyle(el, 'stroke'))
44 | : getStyle(el, 'color')
45 | const background = getComputedBackgroundColor(el)
46 |
47 | const contrastingForegroundColor = contrast_color(foreground)
48 | const contrastingBackgroundColor = contrast_color(background)
49 |
50 | this.style.setProperty('--copy-message-left-color', contrastingForegroundColor)
51 | this.style.setProperty('--copy-message-right-color', contrastingBackgroundColor)
52 |
53 | return `
54 |
55 |
56 | <${el.nodeName.toLowerCase()}>${el.id && '#' + el.id}
57 |
58 |
59 |
60 |
61 |
62 | Foreground
63 |
64 |
65 | ${preferredNotation(foreground, colormode)}
66 |
67 |
68 |
69 |
70 | Background
71 |
72 |
73 | ${preferredNotation(background, colormode)}
74 |
75 |
76 |
77 | ${contrast_results}
78 |
79 | ${ally_attributes.length > 0
80 | ? `
81 |
82 | ${ally_attributes.reduce((items, attr) => `
83 | ${items}
84 | ${attr.prop}:
85 | ${attr.value}
86 | `, '')}
87 |
88 |
`
89 | : ''
90 | }
91 |
92 |
93 | `
94 | }
95 | }
96 |
97 | customElements.define('visbug-ally', Ally)
98 |
--------------------------------------------------------------------------------
/app/components/metatip/metatip.element.js:
--------------------------------------------------------------------------------
1 | import $ from 'blingblingjs'
2 | import { createClassname, schemeRule } from '../../utilities/'
3 | import { draggable } from '../../features/'
4 | import { MetatipStyles, MetatipLightStyles, MetatipDarkStyles } from '../styles.store'
5 |
6 | export class Metatip extends HTMLElement {
7 |
8 | constructor() {
9 | super()
10 | this.$shadow = this.attachShadow({mode: 'closed'})
11 | this.applyScheme = schemeRule(
12 | this.$shadow,
13 | MetatipStyles, MetatipLightStyles, MetatipDarkStyles
14 | )
15 |
16 | this.observe = this.observe.bind(this)
17 | this.dispatchQuery = this.dispatchQuery.bind(this)
18 | this.dispatchUnQuery = this.dispatchUnQuery.bind(this)
19 | }
20 |
21 | connectedCallback() {
22 | this.setAttribute('popover', 'manual')
23 | this.showPopover && this.showPopover()
24 | this.applyScheme(document.querySelector("vis-bug").getAttribute("color-scheme"))
25 | $(this.$shadow.host).on('mouseenter', this.observe)
26 | }
27 |
28 | disconnectedCallback() {
29 | this.unobserve()
30 | this.hidePopover && this.hidePopover()
31 | }
32 |
33 | dispatchQuery(e) {
34 | this.$shadow.host.dispatchEvent(new CustomEvent('query', {
35 | bubbles: true,
36 | detail: {
37 | text: e.target.textContent,
38 | activator: e.type,
39 | }
40 | }))
41 | }
42 |
43 | observe() {
44 | $('h5 > a', this.$shadow).on('click mouseenter', this.dispatchQuery)
45 | $('h5 > a', this.$shadow).on('mouseleave', this.dispatchUnQuery)
46 |
47 | draggable({
48 | el: this,
49 | surface: this.$shadow.querySelector('header'),
50 | cursor: 'grab',
51 | })
52 | }
53 |
54 | unobserve() {
55 | $('h5 > a', this.$shadow).off('click mouseenter', this.dispatchQuery)
56 | $('h5 > a', this.$shadow).off('mouseleave', this.dispatchUnQuery)
57 | }
58 |
59 | dispatchUnQuery(e) {
60 | this.$shadow.host.dispatchEvent(new CustomEvent('unquery', {
61 | bubbles: true
62 | }))
63 | this.unobserve()
64 | this.teardown()
65 | }
66 |
67 | set meta(data) {
68 | this.$shadow.innerHTML = this.render(data)
69 | }
70 |
71 | render({el, width, height, localModifications, notLocalModifications}) {
72 | return `
73 |
74 |
75 |
76 | ${el.nodeName.toLowerCase()}
77 | ${el.id && '#' + el.id}
78 | ${createClassname(el).split('.')
79 | .filter(name => name != '')
80 | .reduce((links, name) => `
81 | ${links}
82 | .${name}
83 | `, '')
84 | }
85 |
86 |
87 | ${Math.round(width)}px
88 | ×
89 | ${Math.round(height)}px
90 |
91 |
92 |
93 | ${notLocalModifications.reduce((items, item) => `
94 | ${items}
95 | ${item.prop}:
96 | ${item.value}
97 | `, '')}
98 |
99 | ${localModifications.length ? `
100 |
101 | Local Modifications / Inline Styles
102 | ${localModifications.reduce((items, item) => `
103 | ${items}
104 | ${item.prop}:
105 | ${item.value}
106 | `, '')}
107 |
108 |
109 | ` : ''}
110 |
111 | `
112 | }
113 | }
114 |
115 | customElements.define('visbug-metatip', Metatip)
116 |
--------------------------------------------------------------------------------
/app/components/metatip/metatip.element_dark.css:
--------------------------------------------------------------------------------
1 | :host {
2 | & [pass="true"] { color: hsl(120deg 50% 75%); }
3 | & [pass="false"] { color: hsl(0deg 50% 65%); }
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/metatip/metatip.element_light.css:
--------------------------------------------------------------------------------
1 | :host {
2 | & [pass="true"] { color: green; }
3 | & [pass="false"] { color: red; }
4 | }
5 |
--------------------------------------------------------------------------------
/app/components/selection/box-model.element.css:
--------------------------------------------------------------------------------
1 | :host [mask] {
2 | pointer-events: none;
3 | position: absolute;
4 | z-index: var(--layer-5);
5 | width: var(--width);
6 | height: var(--height);
7 | top: var(--top);
8 | left: var(--left);
9 | background-color: var(--bg);
10 | clip-path: polygon(
11 | 0% 0%, 0% 100%, var(--target-left) 100%,
12 | var(--target-left) var(--target-top),
13 | var(--offset-right) var(--target-top),
14 | var(--offset-right) var(--offset-bottom),
15 | 0 var(--offset-bottom), 0 100%,
16 | 100% 100%, 100% 0%
17 | );
18 | }
--------------------------------------------------------------------------------
/app/components/selection/corners.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host rect {
4 | width: 5px;
5 | height: 5px;
6 | vector-effect: non-scaling-stroke;
7 | stroke: var(--neon-purple);
8 | stroke-width: 1px;
9 | fill: none;
10 | stroke-linecap: round;
11 | stroke-dasharray: 10px;
12 | stroke-dashoffset: 5px;
13 | x: 1px;
14 | y: 1px;
15 |
16 | &:nth-child(2) {
17 | x: calc(100% - 6px);
18 | y: 1px;
19 | stroke-dashoffset: 0;
20 | }
21 |
22 | &:nth-child(3) {
23 | x: calc(100% - 6px);
24 | y: calc(100% - 6px);
25 | stroke-dashoffset: 15px;
26 | }
27 |
28 | &:nth-child(4) {
29 | x: 1px;
30 | y: calc(100% - 6px);
31 | stroke-dashoffset: 10px;
32 | }
33 | }
34 |
35 | :host > svg {
36 | z-index: var(--layer-5);
37 | }
38 |
--------------------------------------------------------------------------------
/app/components/selection/corners.element.js:
--------------------------------------------------------------------------------
1 | import { Handles } from './handles.element'
2 | import { HandlesStyles, CornerStyles } from '../styles.store'
3 |
4 | export class Corners extends Handles {
5 |
6 | constructor() {
7 | super()
8 | this.styles = [HandlesStyles, CornerStyles]
9 | }
10 |
11 | render({ width, height, top, left }) {
12 | this.style.setProperty('--top', `${top + window.scrollY}px`)
13 | this.style.setProperty('--left', `${left}px`)
14 |
15 | return `
16 |
22 | `
23 | }
24 | }
25 |
26 | customElements.define('visbug-corners', Corners)
27 |
--------------------------------------------------------------------------------
/app/components/selection/distance.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host {
4 | --line-color: 1 0 1;
5 | --line-base: .75 0 1;
6 | --line-width: 1px;
7 | --distance-h: 5px;
8 | --distance-w: 5px;
9 | --line-w: 1px;
10 | --line-h: 1px;
11 | font-size: 16px;
12 | position: initial;
13 | background: transparent;
14 | border: none;
15 | overflow: visible;
16 | padding: 0;
17 | margin: 0;
18 | }
19 |
20 | :host > figure {
21 | margin: 0;
22 | position: absolute;
23 | height: var(--distance-h);
24 | width: var(--distance-w);
25 | inset: var(--top) var(--right) auto var(--left);
26 | overflow: visible;
27 | pointer-events: none;
28 | z-index: var(--layer-3);
29 | display: flex;
30 | align-items: center;
31 | justify-content: var(--justify, 'flex-start');
32 | flex-direction: var(--direction);
33 | }
34 |
35 | :host > figure figcaption {
36 | min-height: 3ex;
37 | width: max-content;
38 | display: inline-flex;
39 | align-items: center;
40 | justify-content: center;
41 | color: white;
42 | text-shadow: var(--text-shadow);
43 | box-shadow: var(--text-shadow);
44 | background: color(display-p3 var(--line-color) / 75%);
45 | border: 1px solid color(display-p3 var(--line-color));
46 | border-radius: 1em;
47 | font-size: 0.7em;
48 | font-weight: bold;
49 | line-height: 1.1;
50 | font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
51 | padding: 0 1ex;
52 | }
53 |
54 | :host > figure span {
55 | background: color(display-p3 var(--line-color));
56 | height: var(--line-h);
57 | width: var(--line-w);
58 | }
59 |
60 | :host > figure div {
61 | flex: 2;
62 | background: color(display-p3 var(--line-color));
63 | width: var(--line-w);
64 | height: var(--line-h);
65 | }
66 |
67 | :host figure:matches([quadrant="bottom"], [quadrant="right"]) > div:first-of-type,
68 | :host figure:matches([quadrant="top"], [quadrant="left"]) > div:last-of-type {
69 | background: linear-gradient(to var(--quadrant), var(--neon-pink) 0%, color(display-p3 var(--line-color)) 100%);
70 | }
71 |
72 | :host::backdrop {
73 | background: none !important;
74 | }
75 |
--------------------------------------------------------------------------------
/app/components/selection/distance.element.js:
--------------------------------------------------------------------------------
1 | import { DistanceStyles } from '../styles.store'
2 |
3 | export class Distance extends HTMLElement {
4 |
5 | constructor() {
6 | super()
7 | this.$shadow = this.attachShadow({mode: 'open'})
8 | }
9 |
10 | connectedCallback() {
11 | this.$shadow.adoptedStyleSheets = [DistanceStyles]
12 | }
13 |
14 | disconnectedCallback() {
15 | if (this.hasAttribute('popover'))
16 | this.hidePopover && this.hidePopover()
17 | }
18 |
19 | set position({line_model, node_label_id}) {
20 | this.styleProps = line_model
21 | this.$shadow.innerHTML = this.render(line_model, node_label_id)
22 | }
23 |
24 | set styleProps({y,x,d,q,v = false, color}) {
25 | this.style.setProperty('--top', `${Math.round(y + window.scrollY)}px`)
26 | this.style.setProperty('--right', 'auto')
27 | this.style.setProperty('--left', `${x}px`)
28 | this.style.setProperty('--direction', v ? 'column' : 'row')
29 | this.style.setProperty('--quadrant', q)
30 |
31 | if (q === 'left')
32 | this.style.setProperty('--justify', 'flex-end')
33 |
34 | v
35 | ? this.style.setProperty('--distance-h', `${d}px`)
36 | : this.style.setProperty('--distance-w', `${d}px`)
37 |
38 | v
39 | ? this.style.setProperty('--line-h', `var(--line-w)`)
40 | : this.style.setProperty('--line-w', `var(--line-w)`)
41 |
42 | this.style.setProperty('--line-color', color === 'pink'
43 | ? '1 0 1'
44 | : '.5 0 1')
45 | this.style.setProperty('--line-base', color === 'pink'
46 | ? '1 0 1'
47 | : '.5 0 1')
48 | }
49 |
50 | render({q,d}, node_label_id) {
51 | this.$shadow.host.setAttribute('data-label-id', node_label_id)
52 |
53 | return `
54 |
55 |
56 | ${Math.round(d)}
57 |
58 |
59 | `
60 | }
61 |
62 | isPopover() {
63 | this.setAttribute('popover', 'manual')
64 | this.showPopover && this.showPopover()
65 | }
66 | }
67 |
68 | customElements.define('visbug-distance', Distance)
69 |
--------------------------------------------------------------------------------
/app/components/selection/gridlines.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host {
4 | position: fixed;
5 | background: transparent;
6 | pointer-events: none;
7 | border: none;
8 | overflow: visible;
9 | padding: 0;
10 | margin: 0;
11 | inset: 0;
12 | width: 100%;
13 | height: 100%;
14 | }
15 |
16 | :host > svg {
17 | position:fixed;
18 | top:0;
19 | left:0;
20 | overflow:visible;
21 | pointer-events:none;
22 | z-index:var(--layer-5);
23 | }
24 |
25 | :host rect,
26 | :host line {
27 | stroke: var(--neon-pink);
28 | }
29 |
30 | :host line {
31 | stroke-dasharray: 2;
32 | stroke-dasharray-offset: 3;
33 | }
34 |
35 | :host::backdrop {
36 | background: none !important;
37 | }
38 |
--------------------------------------------------------------------------------
/app/components/selection/gridlines.element.js:
--------------------------------------------------------------------------------
1 | import { windowBounds } from '../../utilities/'
2 | import { GridlineStyles } from '../styles.store'
3 |
4 | export class Gridlines extends HTMLElement {
5 |
6 | constructor() {
7 | super()
8 | this.$shadow = this.attachShadow({mode: 'closed'})
9 | }
10 |
11 | connectedCallback() {
12 | this.$shadow.adoptedStyleSheets = [GridlineStyles]
13 | this.setAttribute('popover', 'manual')
14 | this.showPopover && this.showPopover()
15 | }
16 |
17 | disconnectedCallback() {
18 | this.hidePopover && this.hidePopover()
19 | }
20 |
21 | set position(boundingRect) {
22 | this.$shadow.innerHTML = this.render(boundingRect)
23 | }
24 |
25 | set update({ width, height, top, left }) {
26 | const { winHeight, winWidth } = windowBounds()
27 | const svg = this.$shadow.querySelector('svg')
28 | const [rect,line1,line2,line3,line4] = svg.children
29 |
30 | this.$shadow.host.style.display = 'block'
31 |
32 | svg.setAttribute('viewBox', `0 0 ${winWidth} ${winHeight}`)
33 | rect.setAttribute('width', width + 'px')
34 | rect.setAttribute('x', left)
35 | rect.setAttribute('y', top)
36 | line1.setAttribute('x1', left)
37 | line1.setAttribute('x2', left)
38 | line2.setAttribute('x1', left + width)
39 | line2.setAttribute('x2', left + width)
40 | line3.setAttribute('y1', top)
41 | line3.setAttribute('y2', top)
42 | line3.setAttribute('x2', winWidth)
43 | line4.setAttribute('y1', top + height)
44 | line4.setAttribute('y2', top + height)
45 | line4.setAttribute('x2', winWidth)
46 | }
47 |
48 | render({ x, y, width, height, top, left }) {
49 | const { winWidth, winHeight } = windowBounds()
50 |
51 | return `
52 |
68 | `
69 | }
70 | }
71 |
72 | customElements.define('visbug-gridlines', Gridlines)
73 |
--------------------------------------------------------------------------------
/app/components/selection/grip.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host g {
4 | transform: translate(calc(50% - 10%), calc(50% - .25em));
5 | }
6 |
7 | :host rect {
8 | vector-effect: non-scaling-stroke;
9 | height: .5em;
10 | width: 20%;
11 | max-width: 10vmax;
12 | stroke: var(--neon-pink);
13 | stroke-width: 1px;
14 | stroke-linecap: round;
15 | rx: 4;
16 | }
17 |
18 | :host > svg {
19 | z-index: var(--layer-5);
20 |
21 | &[hovering] rect {
22 | fill: var(--neon-pink);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/components/selection/grip.element.js:
--------------------------------------------------------------------------------
1 | import { Handles } from './handles.element'
2 | import { HandlesStyles, GripStyles } from '../styles.store'
3 | import { isFixed } from '../../utilities/';
4 |
5 | export class Grip extends Handles {
6 |
7 | constructor() {
8 | super()
9 | this.styles = [HandlesStyles, GripStyles]
10 | }
11 |
12 | toggleHovering({hovering}) {
13 | hovering
14 | ? this.$shadow.children[0].setAttribute('hovering', true)
15 | : this.$shadow.children[0].removeAttribute('hovering')
16 | }
17 |
18 | render({ width, height, top, left }) {
19 | this.style.setProperty('--position', isFixed(this.$shadow.host) ? 'fixed' : 'absolute')
20 | this.style.setProperty('--top', `${top + window.scrollY}px`)
21 | this.style.setProperty('--left', `${left}px`)
22 |
23 | return `
24 |
32 | `
33 | }
34 | }
35 |
36 | customElements.define('visbug-grip', Grip)
37 |
--------------------------------------------------------------------------------
/app/components/selection/handle.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host {
4 | display: grid;
5 | grid-area: 1 / -1;
6 | place-self: var(--align-self, center) var(--justify-self, center);
7 | transform: translate(var(--translate-x, 0), var(--translate-y, 0));
8 | }
9 |
10 | :host([hidden]) {
11 | display: none;
12 | }
13 |
14 | :host > button {
15 | pointer-events: auto;
16 | background-color: white;
17 | border: 1px solid var(--neon-pink);
18 | padding: 0;
19 | width: 0.5rem;
20 | height: 0.5rem;
21 | border-radius: 50%;
22 | position: relative;
23 | cursor: var(--cursor);
24 |
25 | /* increase tap target size */
26 | &::before {
27 | content: '';
28 | position: absolute;
29 | inset: -0.5rem;
30 | }
31 | }
32 |
33 | :host([placement^="top"]) {
34 | --align-self: start;
35 | --translate-y: -50%;
36 | }
37 |
38 | :host([placement^="bottom"]) {
39 | --align-self: end;
40 | --translate-y: 50%;
41 | }
42 |
43 | :host([placement$="start"]) {
44 | --justify-self: start;
45 | --translate-x: -50%;
46 | }
47 |
48 | :host([placement$="end"]) {
49 | --justify-self: end;
50 | --translate-x: 50%;
51 | }
52 |
53 | :host([placement^="top"]),
54 | :host([placement^="bottom"]) {
55 | --cursor: ns-resize;
56 | }
57 |
58 | :host([placement$="start"]),
59 | :host([placement$="end"]) {
60 | --cursor: ew-resize;
61 | }
62 |
63 | :host([placement="top-start"]) {
64 | --cursor: nw-resize;
65 | }
66 |
67 | :host([placement="top-end"]) {
68 | --cursor: ne-resize;
69 | }
70 |
71 | :host([placement="bottom-start"]) {
72 | --cursor: sw-resize;
73 | }
74 |
75 | :host([placement="bottom-end"]) {
76 | --cursor: se-resize;
77 | }
78 |
--------------------------------------------------------------------------------
/app/components/selection/handles.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host {
4 | position: var(--position, 'absolute');
5 | inset: var(--top) auto auto var(--left);
6 | background: transparent;
7 | border: none;
8 | overflow: visible;
9 | padding: 0;
10 | pointer-events: none;
11 | z-index: var(--layer-3);
12 | width: var(--width);
13 | height: var(--height);
14 | display: grid;
15 | grid-template-rows: 1fr;
16 | isolation: isolate;
17 | }
18 |
19 | :host > svg {
20 | position: absolute;
21 | }
22 |
23 | :host::backdrop {
24 | background: none !important;
25 | }
26 |
--------------------------------------------------------------------------------
/app/components/selection/handles.element.js:
--------------------------------------------------------------------------------
1 | import $ from 'blingblingjs'
2 | import { HandlesStyles } from '../styles.store'
3 | import { isFixed } from '../../utilities/';
4 |
5 | export class Handles extends HTMLElement {
6 |
7 | constructor() {
8 | super()
9 | this.$shadow = this.attachShadow({mode: 'closed'})
10 | this.styles = [HandlesStyles]
11 | this.on_resize = this.on_window_resize.bind(this)
12 | }
13 |
14 | connectedCallback() {
15 | this.$shadow.adoptedStyleSheets = this.styles
16 | this.setAttribute('popover', 'manual')
17 | this.showPopover && this.showPopover()
18 | window.addEventListener('resize', this.on_window_resize)
19 | }
20 |
21 | disconnectedCallback() {
22 | if (this.hidePopover && this.hidePopover()) this.hidePopover && this.hidePopover()
23 | window.removeEventListener('resize', this.on_window_resize)
24 | }
25 |
26 | on_window_resize() {
27 | if (!this.$shadow) return
28 | window.requestAnimationFrame(() => {
29 | const node_label_id = this.$shadow.host.getAttribute('data-label-id')
30 | const [source_el] = $(`[data-label-id="${node_label_id}"]`)
31 |
32 | if (!source_el) return
33 |
34 | this.position = {
35 | node_label_id,
36 | el: source_el,
37 | isFixed: isFixed(source_el),
38 | }
39 | })
40 | }
41 |
42 | set position({el, node_label_id}) {
43 | this.$shadow.innerHTML = this.render(el.getBoundingClientRect(), node_label_id, isFixed(el))
44 |
45 | if (this._backdrop) {
46 | this.backdrop = {
47 | element: this._backdrop.update(el),
48 | update: this._backdrop.update,
49 | }
50 | }
51 | }
52 |
53 | set backdrop(bd) {
54 | this._backdrop = bd
55 |
56 | const cur_child = this.$shadow.querySelector('visbug-boxmodel')
57 |
58 | cur_child
59 | ? this.$shadow.replaceChild(bd.element, cur_child)
60 | : this.$shadow.appendChild(bd.element)
61 | }
62 |
63 | render({ x, y, width, height, top, left }, node_label_id, isFixed) {
64 | this.$shadow.host.setAttribute('data-label-id', node_label_id)
65 |
66 | this.style.setProperty('--top', `${top + (isFixed ? 0 : window.scrollY)}px`)
67 | this.style.setProperty('--left', `${left}px`)
68 | this.style.setProperty('--position', isFixed ? 'fixed' : 'absolute')
69 | this.style.setProperty('--width', `${width}px`)
70 | this.style.setProperty('--height', `${height}px`)
71 |
72 | return `
73 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | `
90 | }
91 | }
92 |
93 | customElements.define('visbug-handles', Handles)
94 |
--------------------------------------------------------------------------------
/app/components/selection/hover.element.css:
--------------------------------------------------------------------------------
1 | @import "../_variables.css";
2 |
3 | :host rect {
4 | width: 100%;
5 | height: 100%;
6 | vector-effect: non-scaling-stroke;
7 | stroke: var(--hover-stroke, var(--neon-purple, hsl(267 100% 58%)));
8 | stroke-width: 2px;
9 | fill: none;
10 | }
11 |
12 | :host > svg {
13 | z-index: var(--layer-5);
14 | }
15 |
--------------------------------------------------------------------------------
/app/components/selection/hover.element.js:
--------------------------------------------------------------------------------
1 | import { Handles } from './handles.element'
2 | import { HandlesStyles, HoverStyles } from '../styles.store'
3 |
4 | export class Hover extends Handles {
5 |
6 | constructor() {
7 | super()
8 | this.styles = [HandlesStyles, HoverStyles]
9 | }
10 |
11 | connectedCallback() {
12 | this.$shadow.adoptedStyleSheets = this.styles
13 | }
14 |
15 | disconnectedCallback() {}
16 |
17 | render({ width, height, top, left }, node_label_id, isFixed) {
18 | this.style.setProperty('--top', `${top + (isFixed ? 0 : window.scrollY)}px`)
19 | this.style.setProperty('--left', `${left}px`)
20 | this.style.setProperty('--position', isFixed ? 'fixed' : 'absolute')
21 |
22 | return `
23 |