mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-06-16 20:07:33 +02:00
chore: system
This commit is contained in:
parent
aa928f7094
commit
97c023df91
78 changed files with 15225 additions and 15225 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1 +1 @@
|
||||||
github: 0pandadev
|
github: 0pandadev
|
||||||
|
|
150
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
150
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,75 +1,75 @@
|
||||||
name: "\U0001F41E Bug report"
|
name: "\U0001F41E Bug report"
|
||||||
description: Create a report to help me improve Qopy
|
description: Create a report to help me improve Qopy
|
||||||
labels: [Bug]
|
labels: [Bug]
|
||||||
assignees:
|
assignees:
|
||||||
- 0PandaDEV
|
- 0PandaDEV
|
||||||
body:
|
body:
|
||||||
#
|
#
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this bug report!
|
Thanks for taking the time to fill out this bug report!
|
||||||
#
|
#
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the bug
|
label: Describe the bug
|
||||||
description: A clear and concise description of what the bug is.
|
description: A clear and concise description of what the bug is.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: reproduce
|
id: reproduce
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to reproduce
|
label: Steps to reproduce
|
||||||
description: Steps to reproduce the behavior
|
description: Steps to reproduce the behavior
|
||||||
value: |
|
value: |
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: expected
|
id: expected
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected behavior
|
label: Expected behavior
|
||||||
description: A clear and concise description of what you expected to happen.
|
description: A clear and concise description of what you expected to happen.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: screenshots
|
id: screenshots
|
||||||
attributes:
|
attributes:
|
||||||
label: Screenshots
|
label: Screenshots
|
||||||
description: If applicable, add screenshots to help explain your problem.
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: os
|
id: os
|
||||||
attributes:
|
attributes:
|
||||||
label: Operating system
|
label: Operating system
|
||||||
options:
|
options:
|
||||||
- Windows
|
- Windows
|
||||||
- Linux
|
- Linux
|
||||||
- macOS
|
- macOS
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
label: Version of Qopy
|
label: Version of Qopy
|
||||||
placeholder: e.g. 0.1.0
|
placeholder: e.g. 0.1.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional
|
id: additional
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional context
|
label: Additional context
|
||||||
description: Add any other context about the problem here.
|
description: Add any other context about the problem here.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
38
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -1,19 +1,19 @@
|
||||||
name: "\U0001F4A1 Feature request"
|
name: "\U0001F4A1 Feature request"
|
||||||
description: Suggest an idea for Qopy
|
description: Suggest an idea for Qopy
|
||||||
labels: [Feature]
|
labels: [Feature]
|
||||||
assignees:
|
assignees:
|
||||||
- 0PandaDEV
|
- 0PandaDEV
|
||||||
body:
|
body:
|
||||||
#
|
#
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this feature request!
|
Thanks for taking the time to fill out this feature request!
|
||||||
#
|
#
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe your requested feature
|
label: Describe your requested feature
|
||||||
description: Give as many details as possible about your feature idea.
|
description: Give as many details as possible about your feature idea.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
80
.github/scripts/macOS.sh
vendored
80
.github/scripts/macOS.sh
vendored
|
@ -1,40 +1,40 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
export $(cat .env | grep -v '^#' | xargs)
|
export $(cat .env | grep -v '^#' | xargs)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
required_vars=("APPLE_CERTIFICATE" "APPLE_CERTIFICATE_PASSWORD" "APPLE_ID" "APPLE_ID_PASSWORD" "KEYCHAIN_PASSWORD" "APP_BUNDLE_ID")
|
required_vars=("APPLE_CERTIFICATE" "APPLE_CERTIFICATE_PASSWORD" "APPLE_ID" "APPLE_ID_PASSWORD" "KEYCHAIN_PASSWORD" "APP_BUNDLE_ID")
|
||||||
for var in "${required_vars[@]}"; do
|
for var in "${required_vars[@]}"; do
|
||||||
if [ -z "${!var}" ]; then
|
if [ -z "${!var}" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
bun run tauri build
|
bun run tauri build
|
||||||
|
|
||||||
rm -f certificate.p12
|
rm -f certificate.p12
|
||||||
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 2>/dev/null
|
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 2>/dev/null
|
||||||
security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A 2>/dev/null
|
security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A 2>/dev/null
|
||||||
|
|
||||||
SIGNING_IDENTITY=$(security find-identity -v -p codesigning | grep "Apple Development" | head -1 | awk -F '"' '{print $2}')
|
SIGNING_IDENTITY=$(security find-identity -v -p codesigning | grep "Apple Development" | head -1 | awk -F '"' '{print $2}')
|
||||||
|
|
||||||
if [ -z "$SIGNING_IDENTITY" ]; then
|
if [ -z "$SIGNING_IDENTITY" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
codesign --force --options runtime --sign "$SIGNING_IDENTITY" src-tauri/target/release/bundle/macos/*.app 2>/dev/null
|
codesign --force --options runtime --sign "$SIGNING_IDENTITY" src-tauri/target/release/bundle/macos/*.app 2>/dev/null
|
||||||
|
|
||||||
rm -f certificate.p12
|
rm -f certificate.p12
|
||||||
|
|
||||||
hdiutil create -volname "Qopy" -srcfolder src-tauri/target/release/bundle/dmg -ov -format UDZO Qopy.dmg
|
hdiutil create -volname "Qopy" -srcfolder src-tauri/target/release/bundle/dmg -ov -format UDZO Qopy.dmg
|
||||||
|
|
||||||
codesign --force --sign "$APPLE_CERTIFICATE" Qopy.dmg 2>/dev/null
|
codesign --force --sign "$APPLE_CERTIFICATE" Qopy.dmg 2>/dev/null
|
||||||
|
|
||||||
xcrun notarytool submit Qopy.dmg --apple-id "$APPLE_ID" --password "$APPLE_ID_PASSWORD" --team-id "$APPLE_CERTIFICATE" --wait
|
xcrun notarytool submit Qopy.dmg --apple-id "$APPLE_ID" --password "$APPLE_ID_PASSWORD" --team-id "$APPLE_CERTIFICATE" --wait
|
||||||
|
|
||||||
xcrun stapler staple Qopy.dmg
|
xcrun stapler staple Qopy.dmg
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|
496
.github/workflows/build.yml
vendored
496
.github/workflows/build.yml
vendored
|
@ -1,249 +1,249 @@
|
||||||
name: "Nightly Builds"
|
name: "Nightly Builds"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.get_version.outputs.VERSION }}
|
version: ${{ steps.get_version.outputs.VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: get_version
|
id: get_version
|
||||||
run: echo "VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")" >> $GITHUB_OUTPUT
|
run: echo "VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- args: "--target aarch64-apple-darwin"
|
- args: "--target aarch64-apple-darwin"
|
||||||
arch: "arm64"
|
arch: "arm64"
|
||||||
- args: "--target x86_64-apple-darwin"
|
- args: "--target x86_64-apple-darwin"
|
||||||
arch: "x64"
|
arch: "x64"
|
||||||
env:
|
env:
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Redact Sensitive Information
|
- name: Redact Sensitive Information
|
||||||
run: |
|
run: |
|
||||||
function redact_output {
|
function redact_output {
|
||||||
sed -e "s/${{ secrets.APPLE_ID }}/REDACTED/g;s/${{ secrets.APPLE_ID_PASSWORD }}/REDACTED/g;s/${{ secrets.APPLE_CERTIFICATE }}/REDACTED/g;s/${{ secrets.APPLE_CERTIFICATE_PASSWORD }}/REDACTED/g;s/${{ secrets.KEYCHAIN_PASSWORD }}/REDACTED/g;s/${{ secrets.PAT }}/REDACTED/g;s/${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}/REDACTED/g"
|
sed -e "s/${{ secrets.APPLE_ID }}/REDACTED/g;s/${{ secrets.APPLE_ID_PASSWORD }}/REDACTED/g;s/${{ secrets.APPLE_CERTIFICATE }}/REDACTED/g;s/${{ secrets.APPLE_CERTIFICATE_PASSWORD }}/REDACTED/g;s/${{ secrets.KEYCHAIN_PASSWORD }}/REDACTED/g;s/${{ secrets.PAT }}/REDACTED/g;s/${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}/REDACTED/g"
|
||||||
}
|
}
|
||||||
exec > >(redact_output) 2>&1
|
exec > >(redact_output) 2>&1
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "src-tauri -> target"
|
workspaces: "src-tauri -> target"
|
||||||
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
||||||
shared-key: "macos-rust-cache"
|
shared-key: "macos-rust-cache"
|
||||||
save-if: "true"
|
save-if: "true"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-
|
${{ runner.os }}-pnpm-
|
||||||
- run: npm install -g pnpm && pnpm install
|
- run: npm install -g pnpm && pnpm install
|
||||||
- name: Import Apple Developer Certificate
|
- name: Import Apple Developer Certificate
|
||||||
env:
|
env:
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
||||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
security default-keychain -s build.keychain
|
security default-keychain -s build.keychain
|
||||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
security set-keychain-settings -lut 7200 build.keychain
|
security set-keychain-settings -lut 7200 build.keychain
|
||||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
security find-identity -v -p codesigning build.keychain
|
security find-identity -v -p codesigning build.keychain
|
||||||
- name: Verify Certificate
|
- name: Verify Certificate
|
||||||
run: |
|
run: |
|
||||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Apple Development")
|
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Apple Development")
|
||||||
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
|
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
|
||||||
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
|
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
|
||||||
echo "Certificate imported."
|
echo "Certificate imported."
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
||||||
with:
|
with:
|
||||||
args: ${{ matrix.args }}
|
args: ${{ matrix.args }}
|
||||||
- name: Debug Signing Process
|
- name: Debug Signing Process
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
echo "Attempting manual signing:"
|
echo "Attempting manual signing:"
|
||||||
timeout 300 codesign --force --options runtime --sign "$CERT_ID" --entitlements src-tauri/entitlements.plist src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/macos/Qopy.app
|
timeout 300 codesign --force --options runtime --sign "$CERT_ID" --entitlements src-tauri/entitlements.plist src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/macos/Qopy.app
|
||||||
echo "Verifying signature:"
|
echo "Verifying signature:"
|
||||||
codesign -dv --verbose=4 "src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/macos/Qopy.app" | sed 's/.*Authority=.*/Authority=REDACTED/'
|
codesign -dv --verbose=4 "src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/macos/Qopy.app" | sed 's/.*Authority=.*/Authority=REDACTED/'
|
||||||
- name: Set architecture label
|
- name: Set architecture label
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ matrix.args }}" == "--target aarch64-apple-darwin" ]]; then
|
if [[ "${{ matrix.args }}" == "--target aarch64-apple-darwin" ]]; then
|
||||||
echo "ARCH_LABEL=aarch64-apple-darwin" >> $GITHUB_ENV
|
echo "ARCH_LABEL=aarch64-apple-darwin" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "ARCH_LABEL=x86_64-apple-darwin" >> $GITHUB_ENV
|
echo "ARCH_LABEL=x86_64-apple-darwin" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
- name: Rename and Publish macOS Artifacts
|
- name: Rename and Publish macOS Artifacts
|
||||||
run: |
|
run: |
|
||||||
mv src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/dmg/*.dmg src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/dmg/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.dmg
|
mv src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/dmg/*.dmg src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/dmg/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.dmg
|
||||||
mv src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.app.tar.gz
|
mv src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.app.tar.gz
|
||||||
mv src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz.sig src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.app.tar.gz.sig
|
mv src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz.sig src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.app.tar.gz.sig
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-dmg-${{ matrix.arch }}
|
name: macos-dmg-${{ matrix.arch }}
|
||||||
path: "src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/dmg/*.dmg"
|
path: "src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/dmg/*.dmg"
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: updater-macos-${{ matrix.arch }}
|
name: updater-macos-${{ matrix.arch }}
|
||||||
path: |
|
path: |
|
||||||
src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz
|
src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz
|
||||||
src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz.sig
|
src-tauri/target/${{ env.ARCH_LABEL }}/release/bundle/macos/*.app.tar.gz.sig
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- args: "--target x86_64-pc-windows-msvc"
|
- args: "--target x86_64-pc-windows-msvc"
|
||||||
arch: "x64"
|
arch: "x64"
|
||||||
target: "x86_64-pc-windows-msvc"
|
target: "x86_64-pc-windows-msvc"
|
||||||
- args: "--target aarch64-pc-windows-msvc"
|
- args: "--target aarch64-pc-windows-msvc"
|
||||||
arch: "arm64"
|
arch: "arm64"
|
||||||
target: "aarch64-pc-windows-msvc"
|
target: "aarch64-pc-windows-msvc"
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
env:
|
env:
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
|
targets: x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "src-tauri -> target"
|
workspaces: "src-tauri -> target"
|
||||||
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
||||||
shared-key: "windows-rust-cache"
|
shared-key: "windows-rust-cache"
|
||||||
save-if: "true"
|
save-if: "true"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-
|
${{ runner.os }}-pnpm-
|
||||||
- run: npm install -g pnpm && pnpm install
|
- run: npm install -g pnpm && pnpm install
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: ${{ matrix.args }}
|
args: ${{ matrix.args }}
|
||||||
- name: List Bundle Directory
|
- name: List Bundle Directory
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
Write-Output "Checking build directories..."
|
Write-Output "Checking build directories..."
|
||||||
Get-ChildItem -Path "src-tauri/target" -Recurse -Directory | Where-Object { $_.Name -eq "msi" } | ForEach-Object {
|
Get-ChildItem -Path "src-tauri/target" -Recurse -Directory | Where-Object { $_.Name -eq "msi" } | ForEach-Object {
|
||||||
Write-Output "Found MSI directory: $($_.FullName)"
|
Write-Output "Found MSI directory: $($_.FullName)"
|
||||||
Get-ChildItem -Path $_.FullName -Filter "*.msi" | ForEach-Object {
|
Get-ChildItem -Path $_.FullName -Filter "*.msi" | ForEach-Object {
|
||||||
Write-Output "Found MSI file: $($_.FullName)"
|
Write-Output "Found MSI file: $($_.FullName)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
- name: Rename and Publish Windows Artifacts
|
- name: Rename and Publish Windows Artifacts
|
||||||
run: |
|
run: |
|
||||||
mv src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi src-tauri/target/${{ matrix.target }}/release/bundle/msi/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.msi
|
mv src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi src-tauri/target/${{ matrix.target }}/release/bundle/msi/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.msi
|
||||||
mv src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi.sig src-tauri/target/${{ matrix.target }}/release/bundle/msi/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.msi.sig
|
mv src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi.sig src-tauri/target/${{ matrix.target }}/release/bundle/msi/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.msi.sig
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-${{ matrix.arch }}
|
name: windows-${{ matrix.arch }}
|
||||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
|
path: src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: updater-windows-${{ matrix.arch }}
|
name: updater-windows-${{ matrix.arch }}
|
||||||
path: |
|
path: |
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
|
src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
|
||||||
src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi.sig
|
src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi.sig
|
||||||
|
|
||||||
build-ubuntu:
|
build-ubuntu:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: x86_64-unknown-linux-gnu
|
targets: x86_64-unknown-linux-gnu
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "src-tauri -> target"
|
workspaces: "src-tauri -> target"
|
||||||
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
||||||
shared-key: "ubuntu-rust-cache"
|
shared-key: "ubuntu-rust-cache"
|
||||||
save-if: "true"
|
save-if: "true"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-
|
${{ runner.os }}-pnpm-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libayatana-appindicator3-dev librsvg2-dev libasound2-dev rpm
|
sudo apt install -y libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libayatana-appindicator3-dev librsvg2-dev libasound2-dev rpm
|
||||||
echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig" >> $GITHUB_ENV
|
echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig" >> $GITHUB_ENV
|
||||||
- run: npm install -g pnpm && pnpm install
|
- run: npm install -g pnpm && pnpm install
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: --target x86_64-unknown-linux-gnu
|
args: --target x86_64-unknown-linux-gnu
|
||||||
- name: Rename Linux Artifacts
|
- name: Rename Linux Artifacts
|
||||||
run: |
|
run: |
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/Qopy-${{ needs.prepare.outputs.version }}.deb
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/Qopy-${{ needs.prepare.outputs.version }}.deb
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/Qopy-${{ needs.prepare.outputs.version }}.AppImage
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/Qopy-${{ needs.prepare.outputs.version }}.AppImage
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage.sig src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/Qopy-${{ needs.prepare.outputs.version }}.AppImage.sig
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage.sig src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/Qopy-${{ needs.prepare.outputs.version }}.AppImage.sig
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/Qopy-${{ needs.prepare.outputs.version }}.rpm
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/Qopy-${{ needs.prepare.outputs.version }}.rpm
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-deb
|
name: ubuntu-deb
|
||||||
path: src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
|
path: src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-appimage
|
name: ubuntu-appimage
|
||||||
path: src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
path: src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ubuntu-rpm
|
name: ubuntu-rpm
|
||||||
path: src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
|
path: src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: updater-ubuntu
|
name: updater-ubuntu
|
||||||
path: |
|
path: |
|
||||||
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
||||||
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage.sig
|
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage.sig
|
648
.github/workflows/release.yml
vendored
648
.github/workflows/release.yml
vendored
|
@ -1,325 +1,325 @@
|
||||||
name: "Release"
|
name: "Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.get_version.outputs.VERSION }}
|
version: ${{ steps.get_version.outputs.VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: get_version
|
id: get_version
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(node -p 'require("./src-tauri/tauri.conf.json").version')
|
VERSION=$(node -p 'require("./src-tauri/tauri.conf.json").version')
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
needs: prepare
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- args: "--target aarch64-apple-darwin"
|
- args: "--target aarch64-apple-darwin"
|
||||||
arch: "silicon"
|
arch: "silicon"
|
||||||
- args: "--target x86_64-apple-darwin"
|
- args: "--target x86_64-apple-darwin"
|
||||||
arch: "intel"
|
arch: "intel"
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
env:
|
env:
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Redact Sensitive Information
|
- name: Redact Sensitive Information
|
||||||
run: |
|
run: |
|
||||||
function redact_output {
|
function redact_output {
|
||||||
sed -e "s/${{ secrets.REDACT_PATTERN }}/REDACTED/g"
|
sed -e "s/${{ secrets.REDACT_PATTERN }}/REDACTED/g"
|
||||||
}
|
}
|
||||||
exec > >(redact_output) 2>&1
|
exec > >(redact_output) 2>&1
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
targets: aarch64-apple-darwin,x86_64-apple-darwin
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "src-tauri -> target"
|
workspaces: "src-tauri -> target"
|
||||||
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
||||||
shared-key: "macos-rust-cache"
|
shared-key: "macos-rust-cache"
|
||||||
save-if: "true"
|
save-if: "true"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-
|
${{ runner.os }}-pnpm-
|
||||||
- run: npm install -g pnpm && pnpm install
|
- run: npm install -g pnpm && pnpm install
|
||||||
- name: Import Apple Developer Certificate
|
- name: Import Apple Developer Certificate
|
||||||
env:
|
env:
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
||||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
security default-keychain -s build.keychain
|
security default-keychain -s build.keychain
|
||||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
args: ${{ matrix.args }}
|
args: ${{ matrix.args }}
|
||||||
|
|
||||||
- name: Rename macOS Artifacts
|
- name: Rename macOS Artifacts
|
||||||
run: |
|
run: |
|
||||||
mv src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/dmg/*.dmg src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/dmg/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.dmg
|
mv src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/dmg/*.dmg src-tauri/target/${{ matrix.args == '--target aarch64-apple-darwin' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin' }}/release/bundle/dmg/Qopy-${{ needs.prepare.outputs.version }}_${{ matrix.arch }}.dmg
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-${{ matrix.arch }}-binaries
|
name: macos-${{ matrix.arch }}-binaries
|
||||||
path: |
|
path: |
|
||||||
src-tauri/target/**/release/bundle/dmg/*.dmg
|
src-tauri/target/**/release/bundle/dmg/*.dmg
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
needs: prepare
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- args: "--target x86_64-pc-windows-msvc"
|
- args: "--target x86_64-pc-windows-msvc"
|
||||||
arch: "x64"
|
arch: "x64"
|
||||||
target: "x86_64-pc-windows-msvc"
|
target: "x86_64-pc-windows-msvc"
|
||||||
- args: "--target aarch64-pc-windows-msvc"
|
- args: "--target aarch64-pc-windows-msvc"
|
||||||
arch: "arm64"
|
arch: "arm64"
|
||||||
target: "aarch64-pc-windows-msvc"
|
target: "aarch64-pc-windows-msvc"
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
env:
|
env:
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
|
targets: x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "src-tauri -> target"
|
workspaces: "src-tauri -> target"
|
||||||
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
||||||
shared-key: "windows-rust-cache"
|
shared-key: "windows-rust-cache"
|
||||||
save-if: "true"
|
save-if: "true"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-
|
${{ runner.os }}-pnpm-
|
||||||
- run: npm install -g pnpm && pnpm install
|
- run: npm install -g pnpm && pnpm install
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: ${{ matrix.args }}
|
args: ${{ matrix.args }}
|
||||||
- name: List Bundle Directory
|
- name: List Bundle Directory
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$bundlePath = "src-tauri/target/${{ matrix.target }}/release/bundle/msi"
|
$bundlePath = "src-tauri/target/${{ matrix.target }}/release/bundle/msi"
|
||||||
if (Test-Path $bundlePath) {
|
if (Test-Path $bundlePath) {
|
||||||
Write-Output "Contents of ${bundlePath}:"
|
Write-Output "Contents of ${bundlePath}:"
|
||||||
Get-ChildItem -Path $bundlePath
|
Get-ChildItem -Path $bundlePath
|
||||||
} else {
|
} else {
|
||||||
Write-Output "Path ${bundlePath} does not exist."
|
Write-Output "Path ${bundlePath} does not exist."
|
||||||
}
|
}
|
||||||
- name: Rename Windows Artifacts
|
- name: Rename Windows Artifacts
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$bundlePath = "src-tauri/target/${{ matrix.target }}/release/bundle/msi"
|
$bundlePath = "src-tauri/target/${{ matrix.target }}/release/bundle/msi"
|
||||||
$version = "${{ needs.prepare.outputs.version }}"
|
$version = "${{ needs.prepare.outputs.version }}"
|
||||||
$arch = "${{ matrix.arch }}"
|
$arch = "${{ matrix.arch }}"
|
||||||
if (Test-Path $bundlePath) {
|
if (Test-Path $bundlePath) {
|
||||||
$msiFiles = Get-ChildItem -Path "$bundlePath/*.msi"
|
$msiFiles = Get-ChildItem -Path "$bundlePath/*.msi"
|
||||||
foreach ($file in $msiFiles) {
|
foreach ($file in $msiFiles) {
|
||||||
$newName = "Qopy-$version`_$arch.msi"
|
$newName = "Qopy-$version`_$arch.msi"
|
||||||
Rename-Item -Path $file.FullName -NewName $newName
|
Rename-Item -Path $file.FullName -NewName $newName
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Error "Path ${bundlePath} does not exist."
|
Write-Error "Path ${bundlePath} does not exist."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-${{ matrix.arch }}-binaries
|
name: windows-${{ matrix.arch }}-binaries
|
||||||
path: src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
|
path: src-tauri/target/${{ matrix.target }}/release/bundle/msi/*.msi
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
needs: prepare
|
needs: prepare
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- uses: swatinem/rust-cache@v2
|
- uses: swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: "src-tauri -> target"
|
workspaces: "src-tauri -> target"
|
||||||
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
cache-directories: "~/.cargo/registry/index/,~/.cargo/registry/cache/,~/.cargo/git/db/"
|
||||||
shared-key: "linux-rust-cache"
|
shared-key: "linux-rust-cache"
|
||||||
save-if: "true"
|
save-if: "true"
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-
|
${{ runner.os }}-pnpm-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libayatana-appindicator3-dev librsvg2-dev libasound2-dev rpm
|
sudo apt install -y libwebkit2gtk-4.1-dev build-essential curl wget file libssl-dev libayatana-appindicator3-dev librsvg2-dev libasound2-dev rpm
|
||||||
echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig" >> $GITHUB_ENV
|
echo "PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig" >> $GITHUB_ENV
|
||||||
- run: npm install -g pnpm && pnpm install
|
- run: npm install -g pnpm && pnpm install
|
||||||
- name: Generate Changelog
|
- name: Generate Changelog
|
||||||
id: changelog
|
id: changelog
|
||||||
run: |
|
run: |
|
||||||
CHANGELOG=$(git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"- %s")
|
CHANGELOG=$(git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"- %s")
|
||||||
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
|
echo "CHANGELOG<<EOF" >> $GITHUB_ENV
|
||||||
echo "$CHANGELOG" >> $GITHUB_ENV
|
echo "$CHANGELOG" >> $GITHUB_ENV
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "EOF" >> $GITHUB_ENV
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: --target x86_64-unknown-linux-gnu
|
args: --target x86_64-unknown-linux-gnu
|
||||||
- name: Rename Linux Artifacts
|
- name: Rename Linux Artifacts
|
||||||
run: |
|
run: |
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/Qopy-${{ needs.prepare.outputs.version }}.deb
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/Qopy-${{ needs.prepare.outputs.version }}.deb
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/Qopy-${{ needs.prepare.outputs.version }}.AppImage
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/Qopy-${{ needs.prepare.outputs.version }}.AppImage
|
||||||
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/Qopy-${{ needs.prepare.outputs.version }}.rpm
|
mv src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/Qopy-${{ needs.prepare.outputs.version }}.rpm
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: linux-binaries
|
name: linux-binaries
|
||||||
path: |
|
path: |
|
||||||
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
|
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
|
||||||
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
|
||||||
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
|
src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
|
||||||
|
|
||||||
create-release:
|
create-release:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
needs: [prepare, build-macos, build-windows, build-linux]
|
needs: [prepare, build-macos, build-windows, build-linux]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Check if release already exists
|
- name: Check if release already exists
|
||||||
id: check_release
|
id: check_release
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ needs.prepare.outputs.version }}"
|
VERSION="${{ needs.prepare.outputs.version }}"
|
||||||
RELEASE_EXISTS=$(gh release view v$VERSION --json id --jq '.id' 2>/dev/null || echo "")
|
RELEASE_EXISTS=$(gh release view v$VERSION --json id --jq '.id' 2>/dev/null || echo "")
|
||||||
if [ -n "$RELEASE_EXISTS" ]; then
|
if [ -n "$RELEASE_EXISTS" ]; then
|
||||||
echo "SKIP_RELEASE=true" >> $GITHUB_ENV
|
echo "SKIP_RELEASE=true" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "SKIP_RELEASE=false" >> $GITHUB_ENV
|
echo "SKIP_RELEASE=false" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Update CHANGELOG
|
- name: Update CHANGELOG
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
id: changelog
|
id: changelog
|
||||||
uses: requarks/changelog-action@v1
|
uses: requarks/changelog-action@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
tag: ${{ github.ref_name }}
|
tag: ${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Generate Release Body
|
- name: Generate Release Body
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
id: release_body
|
id: release_body
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ needs.prepare.outputs.version }}"
|
VERSION="${{ needs.prepare.outputs.version }}"
|
||||||
|
|
||||||
# Calculate hashes with corrected paths
|
# Calculate hashes with corrected paths
|
||||||
WINDOWS_ARM_HASH=$(sha256sum "artifacts/windows-arm64-binaries/Qopy-${VERSION}_arm64.msi" | awk '{ print $1 }')
|
WINDOWS_ARM_HASH=$(sha256sum "artifacts/windows-arm64-binaries/Qopy-${VERSION}_arm64.msi" | awk '{ print $1 }')
|
||||||
WINDOWS_64_HASH=$(sha256sum "artifacts/windows-x64-binaries/Qopy-${VERSION}_x64.msi" | awk '{ print $1 }')
|
WINDOWS_64_HASH=$(sha256sum "artifacts/windows-x64-binaries/Qopy-${VERSION}_x64.msi" | awk '{ print $1 }')
|
||||||
MAC_SILICON_HASH=$(sha256sum "artifacts/macos-silicon-binaries/aarch64-apple-darwin/release/bundle/dmg/Qopy-${VERSION}_silicon.dmg" | awk '{ print $1 }')
|
MAC_SILICON_HASH=$(sha256sum "artifacts/macos-silicon-binaries/aarch64-apple-darwin/release/bundle/dmg/Qopy-${VERSION}_silicon.dmg" | awk '{ print $1 }')
|
||||||
MAC_INTEL_HASH=$(sha256sum "artifacts/macos-intel-binaries/x86_64-apple-darwin/release/bundle/dmg/Qopy-${VERSION}_intel.dmg" | awk '{ print $1 }')
|
MAC_INTEL_HASH=$(sha256sum "artifacts/macos-intel-binaries/x86_64-apple-darwin/release/bundle/dmg/Qopy-${VERSION}_intel.dmg" | awk '{ print $1 }')
|
||||||
DEBIAN_HASH=$(sha256sum "artifacts/linux-binaries/deb/Qopy-${VERSION}.deb" | awk '{ print $1 }')
|
DEBIAN_HASH=$(sha256sum "artifacts/linux-binaries/deb/Qopy-${VERSION}.deb" | awk '{ print $1 }')
|
||||||
APPIMAGE_HASH=$(sha256sum "artifacts/linux-binaries/appimage/Qopy-${VERSION}.AppImage" | awk '{ print $1 }')
|
APPIMAGE_HASH=$(sha256sum "artifacts/linux-binaries/appimage/Qopy-${VERSION}.AppImage" | awk '{ print $1 }')
|
||||||
REDHAT_HASH=$(sha256sum "artifacts/linux-binaries/rpm/Qopy-${VERSION}.rpm" | awk '{ print $1 }')
|
REDHAT_HASH=$(sha256sum "artifacts/linux-binaries/rpm/Qopy-${VERSION}.rpm" | awk '{ print $1 }')
|
||||||
|
|
||||||
# Debug output
|
# Debug output
|
||||||
echo "Calculated hashes:"
|
echo "Calculated hashes:"
|
||||||
echo "Windows ARM: $WINDOWS_ARM_HASH"
|
echo "Windows ARM: $WINDOWS_ARM_HASH"
|
||||||
echo "Windows x64: $WINDOWS_64_HASH"
|
echo "Windows x64: $WINDOWS_64_HASH"
|
||||||
echo "Mac Silicon: $MAC_SILICON_HASH"
|
echo "Mac Silicon: $MAC_SILICON_HASH"
|
||||||
echo "Mac Intel: $MAC_INTEL_HASH"
|
echo "Mac Intel: $MAC_INTEL_HASH"
|
||||||
echo "Debian: $DEBIAN_HASH"
|
echo "Debian: $DEBIAN_HASH"
|
||||||
echo "AppImage: $APPIMAGE_HASH"
|
echo "AppImage: $APPIMAGE_HASH"
|
||||||
echo "Red Hat: $REDHAT_HASH"
|
echo "Red Hat: $REDHAT_HASH"
|
||||||
|
|
||||||
RELEASE_BODY=$(cat <<-EOF
|
RELEASE_BODY=$(cat <<-EOF
|
||||||
|
|
||||||
${{ needs.create-release.outputs.changelog }}
|
${{ needs.create-release.outputs.changelog }}
|
||||||
|
|
||||||
## ⬇️ Downloads
|
## ⬇️ Downloads
|
||||||
|
|
||||||
- [Windows (x64)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_x64.msi) - ${WINDOWS_64_HASH}
|
- [Windows (x64)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_x64.msi) - ${WINDOWS_64_HASH}
|
||||||
- [Windows (ARM64)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_arm64.msi) - ${WINDOWS_ARM_HASH}
|
- [Windows (ARM64)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_arm64.msi) - ${WINDOWS_ARM_HASH}
|
||||||
- [macOS (Silicon)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_silicon.dmg) - ${MAC_SILICON_HASH}
|
- [macOS (Silicon)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_silicon.dmg) - ${MAC_SILICON_HASH}
|
||||||
- [macOS (Intel)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_intel.dmg) - ${MAC_INTEL_HASH}
|
- [macOS (Intel)](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}_intel.dmg) - ${MAC_INTEL_HASH}
|
||||||
- [Debian](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}.deb) - ${DEBIAN_HASH}
|
- [Debian](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}.deb) - ${DEBIAN_HASH}
|
||||||
- [AppImage](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}.AppImage) - ${APPIMAGE_HASH}
|
- [AppImage](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}.AppImage) - ${APPIMAGE_HASH}
|
||||||
- [Red Hat](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}.rpm) - ${REDHAT_HASH}
|
- [Red Hat](https://github.com/${{ github.repository }}/releases/download/v${VERSION}/Qopy-${VERSION}.rpm) - ${REDHAT_HASH}
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "RELEASE_BODY<<EOF" >> $GITHUB_ENV
|
echo "RELEASE_BODY<<EOF" >> $GITHUB_ENV
|
||||||
echo "$RELEASE_BODY" >> $GITHUB_ENV
|
echo "$RELEASE_BODY" >> $GITHUB_ENV
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
if: env.SKIP_RELEASE == 'false'
|
if: env.SKIP_RELEASE == 'false'
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
tag_name: v${{ needs.prepare.outputs.version }}
|
tag_name: v${{ needs.prepare.outputs.version }}
|
||||||
name: v${{ needs.prepare.outputs.version }}
|
name: v${{ needs.prepare.outputs.version }}
|
||||||
files: |
|
files: |
|
||||||
artifacts/**/*.dmg
|
artifacts/**/*.dmg
|
||||||
artifacts/**/*.msi
|
artifacts/**/*.msi
|
||||||
artifacts/**/*.deb
|
artifacts/**/*.deb
|
||||||
artifacts/**/*.AppImage
|
artifacts/**/*.AppImage
|
||||||
artifacts/**/*.rpm
|
artifacts/**/*.rpm
|
||||||
body: ${{ env.RELEASE_BODY }}
|
body: ${{ env.RELEASE_BODY }}
|
54
.gitignore
vendored
54
.gitignore
vendored
|
@ -1,28 +1,28 @@
|
||||||
# Nuxt dev/build outputs
|
# Nuxt dev/build outputs
|
||||||
.output
|
.output
|
||||||
.data
|
.data
|
||||||
.nuxt
|
.nuxt
|
||||||
.nitro
|
.nitro
|
||||||
.cache
|
.cache
|
||||||
dist
|
dist
|
||||||
|
|
||||||
# Node dependencies
|
# Node dependencies
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.fleet
|
.fleet
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
# Local env files
|
# Local env files
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
bun.lockb
|
bun.lockb
|
||||||
.gitignore
|
.gitignore
|
||||||
.vscode
|
.vscode
|
||||||
bun.lock
|
bun.lock
|
|
@ -1,23 +1,23 @@
|
||||||
# Get Started
|
# Get Started
|
||||||
|
|
||||||
The default hotkey for Qopy is Windows+V which is also the hotkey for the default clipboard manager to turn that off follow [this guide](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md#disable-windowsv-for-default-clipboard-manager).
|
The default hotkey for Qopy is Windows+V which is also the hotkey for the default clipboard manager to turn that off follow [this guide](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md#disable-windowsv-for-default-clipboard-manager).
|
||||||
|
|
||||||
All the data of Qopy is stored inside of a SQLite database.
|
All the data of Qopy is stored inside of a SQLite database.
|
||||||
|
|
||||||
| Operating System | Path |
|
| Operating System | Path |
|
||||||
|------------------|-----------------------------------------------------------------|
|
|------------------|-----------------------------------------------------------------|
|
||||||
| Windows | `C:\Users\USERNAME\AppData\Roaming\net.pandadev.qopy` |
|
| Windows | `C:\Users\USERNAME\AppData\Roaming\net.pandadev.qopy` |
|
||||||
| macOS | `/Users/USERNAME/Library/Application Support/net.pandadev.qopy` |
|
| macOS | `/Users/USERNAME/Library/Application Support/net.pandadev.qopy` |
|
||||||
| Linux | `/home/USERNAME/.local/share/net.pandadev.qopy` |
|
| Linux | `/home/USERNAME/.local/share/net.pandadev.qopy` |
|
||||||
|
|
||||||
## Disable Windows+V for default clipboard manager
|
## Disable Windows+V for default clipboard manager
|
||||||
|
|
||||||
<video src="https://github.com/user-attachments/assets/723f9e07-3190-46ec-9bb7-15dfc112f620" controls title="Disable Windows+V for default clipboard manager"></video>
|
<video src="https://github.com/user-attachments/assets/723f9e07-3190-46ec-9bb7-15dfc112f620" controls title="Disable Windows+V for default clipboard manager"></video>
|
||||||
|
|
||||||
To disable the default clipboard manager popup from windows open Command prompt and run this command
|
To disable the default clipboard manager popup from windows open Command prompt and run this command
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\System" /v AllowClipboardHistory /t REG_DWORD /d 0 /f
|
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\System" /v AllowClipboardHistory /t REG_DWORD /d 0 /f
|
||||||
```
|
```
|
||||||
|
|
||||||
After that a restart may be reqired.
|
After that a restart may be reqired.
|
||||||
|
|
258
README.md
258
README.md
|
@ -1,129 +1,129 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img align="center" width="128px" src="src-tauri/icons/icon.png" />
|
<img align="center" width="128px" src="src-tauri/icons/icon.png" />
|
||||||
<h1 align="center"><b>Qopy</b></h1>
|
<h1 align="center"><b>Qopy</b></h1>
|
||||||
|
|
||||||
The fixed and simple clipboard manager for both Windows and Linux.
|
The fixed and simple clipboard manager for both Windows and Linux.
|
||||||
|
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_x64.msi">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_x64.msi">
|
||||||
<img src="./public/windows.png"> Windows (x64)
|
<img src="./public/windows.png"> Windows (x64)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_arm64.msi">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_arm64.msi">
|
||||||
Windows (arm64)
|
Windows (arm64)
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3.deb">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3.deb">
|
||||||
<img src="./public/linux.png"> Linux (deb)
|
<img src="./public/linux.png"> Linux (deb)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3.rpm">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3.rpm">
|
||||||
Linux (rpm)
|
Linux (rpm)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3.AppImage">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3.AppImage">
|
||||||
Linux (AppImage)
|
Linux (AppImage)
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_silicon.dmg">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_silicon.dmg">
|
||||||
<img src="./public/apple.png"> macOS (Silicon)
|
<img src="./public/apple.png"> macOS (Silicon)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_intel.dmg">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.3/Qopy-0.3.3_intel.dmg">
|
||||||
macOS (Intel)
|
macOS (Intel)
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<sup>Nightly releases can be found <a href="https://github.com/0PandaDEV/qopy/actions/workflows/build.yml">here</a> </sup>
|
<sup>Nightly releases can be found <a href="https://github.com/0PandaDEV/qopy/actions/workflows/build.yml">here</a> </sup>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[discord »](https://discord.gg/invite/Y7SbYphVw9)
|
[discord »](https://discord.gg/invite/Y7SbYphVw9)
|
||||||
|
|
||||||
> \[!IMPORTANT]
|
> \[!IMPORTANT]
|
||||||
>
|
>
|
||||||
> **Star this project**, You will receive all release notifications from GitHub without any delay \~ ⭐️
|
> **Star this project**, You will receive all release notifications from GitHub without any delay \~ ⭐️
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><kbd>Star History</kbd></summary>
|
<summary><kbd>Star History</kbd></summary>
|
||||||
<a href="https://starchart.cc/0PandaDEV/Qopy">
|
<a href="https://starchart.cc/0PandaDEV/Qopy">
|
||||||
<picture>
|
<picture>
|
||||||
<img width="100%" src="https://starchart.cc/0PandaDEV/Qopy.svg?variant=adaptive">
|
<img width="100%" src="https://starchart.cc/0PandaDEV/Qopy.svg?variant=adaptive">
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
[](https://wakatime.com/badge/user/018ce503-097f-4057-9599-db20b190920c/project/fe76359d-56c2-4a13-8413-55207b6ad298)
|
[](https://wakatime.com/badge/user/018ce503-097f-4057-9599-db20b190920c/project/fe76359d-56c2-4a13-8413-55207b6ad298)
|
||||||
|
|
||||||
## 📋 What is Qopy
|
## 📋 What is Qopy
|
||||||
|
|
||||||
Qopy is a fixed clipboard manager designed as a simple alternative to the standard clipboard on Windows. It aims to provide a faster, more reliable experience while providing an extensive set of features compared to its Windows counterpart.
|
Qopy is a fixed clipboard manager designed as a simple alternative to the standard clipboard on Windows. It aims to provide a faster, more reliable experience while providing an extensive set of features compared to its Windows counterpart.
|
||||||
|
|
||||||
## 🚧 Roadmap
|
## 🚧 Roadmap
|
||||||
- [x] [Setup guide](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md)
|
- [x] [Setup guide](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md)
|
||||||
- [ ] Sync Clipboard across devices https://github.com/0PandaDEV/Qopy/issues/8
|
- [ ] Sync Clipboard across devices https://github.com/0PandaDEV/Qopy/issues/8
|
||||||
- [x] Settings https://github.com/0PandaDEV/Qopy/issues/2
|
- [x] Settings https://github.com/0PandaDEV/Qopy/issues/2
|
||||||
- [x] Metadata for copied items https://github.com/0PandaDEV/Qopy/issues/5
|
- [x] Metadata for copied items https://github.com/0PandaDEV/Qopy/issues/5
|
||||||
- [ ] Code highlighting https://github.com/0PandaDEV/Qopy/issues/7
|
- [ ] Code highlighting https://github.com/0PandaDEV/Qopy/issues/7
|
||||||
- [ ] Streamshare integration https://github.com/0PandaDEV/Qopy/issues/4
|
- [ ] Streamshare integration https://github.com/0PandaDEV/Qopy/issues/4
|
||||||
- [ ] Content type filter https://github.com/0PandaDEV/Qopy/issues/16
|
- [ ] Content type filter https://github.com/0PandaDEV/Qopy/issues/16
|
||||||
- [ ] Preview for copied files https://github.com/0PandaDEV/Qopy/issues/15
|
- [ ] Preview for copied files https://github.com/0PandaDEV/Qopy/issues/15
|
||||||
- [ ] Convert files to other formats https://github.com/0PandaDEV/Qopy/issues/17
|
- [ ] Convert files to other formats https://github.com/0PandaDEV/Qopy/issues/17
|
||||||
- [x] Option for custom keybind https://github.com/0PandaDEV/Qopy/issues/3
|
- [x] Option for custom keybind https://github.com/0PandaDEV/Qopy/issues/3
|
||||||
- [x] macOS Support https://github.com/0PandaDEV/Qopy/issues/13
|
- [x] macOS Support https://github.com/0PandaDEV/Qopy/issues/13
|
||||||
|
|
||||||
<sup>If you have ideas for features to include, please write a feature request [here](https://github.com/0pandadev/Qopy/issues).</sup>
|
<sup>If you have ideas for features to include, please write a feature request [here](https://github.com/0pandadev/Qopy/issues).</sup>
|
||||||
|
|
||||||
## 📦 Concepts
|
## 📦 Concepts
|
||||||
|
|
||||||
Here you can see a few concepts these might not be implemented:
|
Here you can see a few concepts these might not be implemented:
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## ❤️ Donations & Support
|
## ❤️ Donations & Support
|
||||||
|
|
||||||
Qopy is open-source and free to use. I appreciate donations to support ongoing development and improvements. Your contributions are voluntary and help me enhance the app for everyone.
|
Qopy is open-source and free to use. I appreciate donations to support ongoing development and improvements. Your contributions are voluntary and help me enhance the app for everyone.
|
||||||
|
|
||||||
<a href="https://buymeacoffee.com/pandadev_"><img src="https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black"/></a>
|
<a href="https://buymeacoffee.com/pandadev_"><img src="https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black"/></a>
|
||||||
|
|
||||||
## ⌨️ Local development
|
## ⌨️ Local development
|
||||||
|
|
||||||
You can use GitHub Codespaces for online development:
|
You can use GitHub Codespaces for online development:
|
||||||
|
|
||||||
[![][codespaces-shield]][codespaces-link]
|
[![][codespaces-shield]][codespaces-link]
|
||||||
|
|
||||||
Or to get Qopy set up on your machine, you'll need to have Rust and bun installed. Then, follow these steps:
|
Or to get Qopy set up on your machine, you'll need to have Rust and bun installed. Then, follow these steps:
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
git clone https://github.com/0pandadev/Qopy.git
|
git clone https://github.com/0pandadev/Qopy.git
|
||||||
cd Qopy
|
cd Qopy
|
||||||
bun i
|
bun i
|
||||||
bun dev
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> \[!TIP]
|
> \[!TIP]
|
||||||
>
|
>
|
||||||
> If you are interested in contributing code, feel free to check out the [Issues](https://github.com/0pandadev/Qopy/issues) section.
|
> If you are interested in contributing code, feel free to check out the [Issues](https://github.com/0pandadev/Qopy/issues) section.
|
||||||
|
|
||||||
## 🔨 Building for production
|
## 🔨 Building for production
|
||||||
|
|
||||||
To build for production simply execute:
|
To build for production simply execute:
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
bun build
|
bun build
|
||||||
```
|
```
|
||||||
|
|
||||||
> \[!NOTE]
|
> \[!NOTE]
|
||||||
>
|
>
|
||||||
> Don't worry, it will fail at the end because it can not detect a Private key, but the installer files will be generated regardless of that.
|
> Don't worry, it will fail at the end because it can not detect a Private key, but the installer files will be generated regardless of that.
|
||||||
>
|
>
|
||||||
> You can find them in `src-tauri/target/release/bundle`.
|
> You can find them in `src-tauri/target/release/bundle`.
|
||||||
|
|
||||||
## 📝 License
|
## 📝 License
|
||||||
|
|
||||||
Qopy is licensed under AGPL-3. See the [LICENSE file](./LICENCE) for more information.
|
Qopy is licensed under AGPL-3. See the [LICENSE file](./LICENCE) for more information.
|
||||||
|
|
||||||
[codespaces-link]: https://codespaces.new/0pandadev/Qopy
|
[codespaces-link]: https://codespaces.new/0pandadev/Qopy
|
||||||
[codespaces-shield]: https://github.com/codespaces/badge.svg
|
[codespaces-shield]: https://github.com/codespaces/badge.svg
|
||||||
|
|
260
README_ru.md
260
README_ru.md
|
@ -1,130 +1,130 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img align="center" width="128px" src="src-tauri/icons/icon.png" />
|
<img align="center" width="128px" src="src-tauri/icons/icon.png" />
|
||||||
<h1 align="center"><b>Qopy</b></h1>
|
<h1 align="center"><b>Qopy</b></h1>
|
||||||
|
|
||||||
Простой и исправленный менеджер буфера обмена как для Windows, так и для Linux.
|
Простой и исправленный менеджер буфера обмена как для Windows, так и для Linux.
|
||||||
|
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_x64.msi">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_x64.msi">
|
||||||
<img src="./public/windows.png"> Windows (x64)
|
<img src="./public/windows.png"> Windows (x64)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_arm64.msi">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_arm64.msi">
|
||||||
Windows (arm64)
|
Windows (arm64)
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1.deb">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1.deb">
|
||||||
<img src="./public/linux.png"> Linux (deb)
|
<img src="./public/linux.png"> Linux (deb)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1.rpm">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1.rpm">
|
||||||
Linux (rpm)
|
Linux (rpm)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1.AppImage">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1.AppImage">
|
||||||
Linux (AppImage)
|
Linux (AppImage)
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_silicon.dmg">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_silicon.dmg">
|
||||||
<img src="./public/apple.png"> macOS (Silicon)
|
<img src="./public/apple.png"> macOS (Silicon)
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_intel.dmg">
|
<a href="https://github.com/0PandaDEV/Qopy/releases/download/v0.3.1/Qopy-0.3.1_intel.dmg">
|
||||||
macOS (Intel)
|
macOS (Intel)
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<sup>Тестовые версии можно найти <a href="https://github.com/0PandaDEV/qopy/actions/workflows/build.yml">тут</a> </sup>
|
<sup>Тестовые версии можно найти <a href="https://github.com/0PandaDEV/qopy/actions/workflows/build.yml">тут</a> </sup>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
[discord »](https://discord.gg/invite/Y7SbYphVw9)
|
[discord »](https://discord.gg/invite/Y7SbYphVw9)
|
||||||
|
|
||||||
> \[!IMPORTANT]
|
> \[!IMPORTANT]
|
||||||
>
|
>
|
||||||
> **Нажав на звезду**, Вы будете получать все уведомления от Github о новых версиях без задержек \~ ⭐️
|
> **Нажав на звезду**, Вы будете получать все уведомления от Github о новых версиях без задержек \~ ⭐️
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><kbd>Star History</kbd></summary>
|
<summary><kbd>Star History</kbd></summary>
|
||||||
<a href="https://star-history.com/#0pandadev/qopy&Date">
|
<a href="https://star-history.com/#0pandadev/qopy&Date">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=0pandadev/qopy&theme=dark&type=Date">
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=0pandadev/qopy&theme=dark&type=Date">
|
||||||
<img width="100%" src="https://api.star-history.com/svg?repos=0pandadev/qopy&type=Date">
|
<img width="100%" src="https://api.star-history.com/svg?repos=0pandadev/qopy&type=Date">
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
[](https://wakatime.com/badge/user/018ce503-097f-4057-9599-db20b190920c/project/fe76359d-56c2-4a13-8413-55207b6ad298)
|
[](https://wakatime.com/badge/user/018ce503-097f-4057-9599-db20b190920c/project/fe76359d-56c2-4a13-8413-55207b6ad298)
|
||||||
|
|
||||||
## 📋 Что такое Qopy
|
## 📋 Что такое Qopy
|
||||||
|
|
||||||
Qopy представляет собой исправленный менеджер буфера обмена, разработанный как простая альтернатива стандартному буферу обмена в Windows. Его цель - обеспечить более быструю и надежную работу, предоставляя при этом обширный набор функций по сравнению со своим аналогом в Windows.
|
Qopy представляет собой исправленный менеджер буфера обмена, разработанный как простая альтернатива стандартному буферу обмена в Windows. Его цель - обеспечить более быструю и надежную работу, предоставляя при этом обширный набор функций по сравнению со своим аналогом в Windows.
|
||||||
|
|
||||||
## 🚧 Дорожная карта
|
## 🚧 Дорожная карта
|
||||||
- [ ] [Руководство по установке](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md)
|
- [ ] [Руководство по установке](https://github.com/0PandaDEV/Qopy/blob/main/GET_STARTED.md)
|
||||||
- [ ] Синхронизация буфера обмена между устройствами https://github.com/0PandaDEV/Qopy/issues/8
|
- [ ] Синхронизация буфера обмена между устройствами https://github.com/0PandaDEV/Qopy/issues/8
|
||||||
- [ ] Настройки https://github.com/0PandaDEV/Qopy/issues/2
|
- [ ] Настройки https://github.com/0PandaDEV/Qopy/issues/2
|
||||||
- [x] Метаданные для скопированных элементов https://github.com/0PandaDEV/Qopy/issues/5
|
- [x] Метаданные для скопированных элементов https://github.com/0PandaDEV/Qopy/issues/5
|
||||||
- [ ] Выделение кода https://github.com/0PandaDEV/Qopy/issues/7
|
- [ ] Выделение кода https://github.com/0PandaDEV/Qopy/issues/7
|
||||||
- [ ] Интеграция Streamshare https://github.com/0PandaDEV/Qopy/issues/4
|
- [ ] Интеграция Streamshare https://github.com/0PandaDEV/Qopy/issues/4
|
||||||
- [ ] Фильтр типов контента https://github.com/0PandaDEV/Qopy/issues/16
|
- [ ] Фильтр типов контента https://github.com/0PandaDEV/Qopy/issues/16
|
||||||
- [ ] Превью для скопированных файлов https://github.com/0PandaDEV/Qopy/issues/15
|
- [ ] Превью для скопированных файлов https://github.com/0PandaDEV/Qopy/issues/15
|
||||||
- [ ] Конвертация файлов в другие форматы https://github.com/0PandaDEV/Qopy/issues/17
|
- [ ] Конвертация файлов в другие форматы https://github.com/0PandaDEV/Qopy/issues/17
|
||||||
- [x] Опция для пользовательской привязки клавиш https://github.com/0PandaDEV/Qopy/issues/3
|
- [x] Опция для пользовательской привязки клавиш https://github.com/0PandaDEV/Qopy/issues/3
|
||||||
- [x] Поддержка macOS https://github.com/0PandaDEV/Qopy/issues/13
|
- [x] Поддержка macOS https://github.com/0PandaDEV/Qopy/issues/13
|
||||||
|
|
||||||
<sup>Если у вас есть идеи для функций, которые можно добавить в будущем, пожалуйста, напишите об этом [здесь](https://github.com/0pandadev/Qopy/issues).</sup>
|
<sup>Если у вас есть идеи для функций, которые можно добавить в будущем, пожалуйста, напишите об этом [здесь](https://github.com/0pandadev/Qopy/issues).</sup>
|
||||||
|
|
||||||
## 📦 Концепты
|
## 📦 Концепты
|
||||||
|
|
||||||
Здесь вы можете увидеть несколько концепцов, которые могут быть не реализованы:
|
Здесь вы можете увидеть несколько концепцов, которые могут быть не реализованы:
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## ❤️ Пожертвования и Поддержка
|
## ❤️ Пожертвования и Поддержка
|
||||||
|
|
||||||
Qopy имеет открытый исходный код и бесплатен для использования. Я ценю пожертвования в поддержку постоянной разработки и улучшений. Ваши взносы являются добровольными и помогают мне улучшить приложение для всех.
|
Qopy имеет открытый исходный код и бесплатен для использования. Я ценю пожертвования в поддержку постоянной разработки и улучшений. Ваши взносы являются добровольными и помогают мне улучшить приложение для всех.
|
||||||
|
|
||||||
<a href="https://buymeacoffee.com/pandadev_"><img src="https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black"/></a>
|
<a href="https://buymeacoffee.com/pandadev_"><img src="https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black"/></a>
|
||||||
|
|
||||||
## ⌨️ Локальная разработка
|
## ⌨️ Локальная разработка
|
||||||
|
|
||||||
Вы можете использовать GitHub Codespaces для онлайн-разработки:
|
Вы можете использовать GitHub Codespaces для онлайн-разработки:
|
||||||
|
|
||||||
[![][codespaces-shield]][codespaces-link]
|
[![][codespaces-shield]][codespaces-link]
|
||||||
|
|
||||||
Или, чтобы настроить Qopy на вашем компьютере, вам необходимо установить Rust и bun. Затем выполните следующие действия:
|
Или, чтобы настроить Qopy на вашем компьютере, вам необходимо установить Rust и bun. Затем выполните следующие действия:
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
git clone https://github.com/0pandadev/Qopy.git
|
git clone https://github.com/0pandadev/Qopy.git
|
||||||
cd Qopy
|
cd Qopy
|
||||||
bun i
|
bun i
|
||||||
bun dev
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> \[!Tip]
|
> \[!Tip]
|
||||||
>
|
>
|
||||||
> Если вы заинтересованы во внесении кода, не стесняйтесь смотреть здесь [Issues](https://github.com/0pandadev/Qopy/issues).
|
> Если вы заинтересованы во внесении кода, не стесняйтесь смотреть здесь [Issues](https://github.com/0pandadev/Qopy/issues).
|
||||||
|
|
||||||
## 🔨 Сборка для продакшена
|
## 🔨 Сборка для продакшена
|
||||||
|
|
||||||
Чтобы собрать для продакшена,просто выполните:
|
Чтобы собрать для продакшена,просто выполните:
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
bun build
|
bun build
|
||||||
```
|
```
|
||||||
|
|
||||||
> \[!NOTE]
|
> \[!NOTE]
|
||||||
>
|
>
|
||||||
> Не волнуйтесь, в конце произойдет сбой, потому что он не сможет обнаружить Приватный ключ, но установочные файлы будут сгенерированы независимо от этого.
|
> Не волнуйтесь, в конце произойдет сбой, потому что он не сможет обнаружить Приватный ключ, но установочные файлы будут сгенерированы независимо от этого.
|
||||||
>
|
>
|
||||||
> Вы можете найти его в `src-tauri/target/release/bundle`.
|
> Вы можете найти его в `src-tauri/target/release/bundle`.
|
||||||
|
|
||||||
## 📝 Лицензия
|
## 📝 Лицензия
|
||||||
|
|
||||||
Qopy лицензирован под GPL-3. Смотрите [LICENSE file](./LICENCE) для дополнительной информации.
|
Qopy лицензирован под GPL-3. Смотрите [LICENSE file](./LICENCE) для дополнительной информации.
|
||||||
|
|
||||||
[codespaces-link]: https://codespaces.new/0pandadev/Qopy
|
[codespaces-link]: https://codespaces.new/0pandadev/Qopy
|
||||||
[codespaces-shield]: https://github.com/codespaces/badge.svg
|
[codespaces-shield]: https://github.com/codespaces/badge.svg
|
||||||
|
|
218
app.vue
218
app.vue
|
@ -1,109 +1,109 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Noise />
|
<Noise />
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { app, window } from "@tauri-apps/api";
|
import { app, window } from "@tauri-apps/api";
|
||||||
import { disable, enable } from "@tauri-apps/plugin-autostart";
|
import { disable, enable } from "@tauri-apps/plugin-autostart";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
import { keyboard } from "wrdu-keyboard";
|
import { keyboard } from "wrdu-keyboard";
|
||||||
|
|
||||||
const { $settings } = useNuxtApp();
|
const { $settings } = useNuxtApp();
|
||||||
keyboard.init();
|
keyboard.init();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await listen("settings", async () => {
|
await listen("settings", async () => {
|
||||||
await navigateTo("/settings");
|
await navigateTo("/settings");
|
||||||
await app.show();
|
await app.show();
|
||||||
await window.getCurrentWindow().show();
|
await window.getCurrentWindow().show();
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((await $settings.getSetting("autostart")) === "true") {
|
if ((await $settings.getSetting("autostart")) === "true") {
|
||||||
await enable();
|
await enable();
|
||||||
} else {
|
} else {
|
||||||
await disable();
|
await disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
await listen("main_route", async () => {
|
await listen("main_route", async () => {
|
||||||
await navigateTo("/");
|
await navigateTo("/");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: SFRoundedRegular;
|
font-family: SFRoundedRegular;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("/fonts/SFRoundedRegular.otf") format("opentype");
|
src: url("/fonts/SFRoundedRegular.otf") format("opentype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("/fonts/SFRoundedMedium.otf") format("opentype");
|
src: url("/fonts/SFRoundedMedium.otf") format("opentype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: SFRoundedSemiBold;
|
font-family: SFRoundedSemiBold;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("/fonts/SFRoundedSemiBold.otf") format("opentype");
|
src: url("/fonts/SFRoundedSemiBold.otf") format("opentype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: CommitMono;
|
font-family: CommitMono;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url("/fonts/CommitMono.woff2") format("woff2");
|
src: url("/fonts/CommitMono.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #2e2d2b;
|
--background: #2e2d2b;
|
||||||
--accent: #feb453;
|
--accent: #feb453;
|
||||||
--border: #ffffff0d;
|
--border: #ffffff0d;
|
||||||
|
|
||||||
--red: #F84E4E;
|
--red: #F84E4E;
|
||||||
|
|
||||||
--text: #e5dfd5;
|
--text: #e5dfd5;
|
||||||
--text-secondary: #ada9a1;
|
--text-secondary: #ada9a1;
|
||||||
--text-muted: #78756f;
|
--text-muted: #78756f;
|
||||||
|
|
||||||
--sidebar-width: 286px;
|
--sidebar-width: 286px;
|
||||||
--bottom-bar-height: 39px;
|
--bottom-bar-height: 39px;
|
||||||
--info-panel-height: 160px;
|
--info-panel-height: 160px;
|
||||||
--content-view-height: calc(
|
--content-view-height: calc(
|
||||||
100% - var(--search-height) - var(--info-panel-height) -
|
100% - var(--search-height) - var(--info-panel-height) -
|
||||||
var(--bottom-bar-height)
|
var(--bottom-bar-height)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-family: SFRoundedRegular;
|
font-family: SFRoundedRegular;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
--os-handle-bg: #ada9a1;
|
--os-handle-bg: #ada9a1;
|
||||||
--os-handle-bg-hover: #78756f;
|
--os-handle-bg-hover: #78756f;
|
||||||
--os-handle-bg-active: #78756f;
|
--os-handle-bg-active: #78756f;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: 750px;
|
width: 750px;
|
||||||
height: 474px;
|
height: 474px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.os-scrollbar-horizontal {
|
.os-scrollbar-horizontal {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,152 +1,152 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bottombar">
|
<div class="bottombar">
|
||||||
<div class="branding">
|
<div class="branding">
|
||||||
<img src="/logo.png" alt="logo" class="logo" />
|
<img src="/logo.png" alt="logo" class="logo" />
|
||||||
<p class="name">Qopy</p>
|
<p class="name">Qopy</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<div v-if="primaryAction" class="paste" @click="handlePrimaryClick">
|
<div v-if="primaryAction" class="paste" @click="handlePrimaryClick">
|
||||||
<p class="text">{{ primaryAction.text }}</p>
|
<p class="text">{{ primaryAction.text }}</p>
|
||||||
<div class="keys">
|
<div class="keys">
|
||||||
<Key v-if="(os === 'windows' || os === 'linux') && primaryAction.showModifier" :input="'Ctrl'" />
|
<Key v-if="(os === 'windows' || os === 'linux') && primaryAction.showModifier" :input="'Ctrl'" />
|
||||||
<IconsCmd v-if="os === 'macos' && primaryAction.showModifier" />
|
<IconsCmd v-if="os === 'macos' && primaryAction.showModifier" />
|
||||||
<component :is="primaryAction.icon" :input="primaryAction.input" />
|
<component :is="primaryAction.icon" :input="primaryAction.input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="secondaryAction" class="divider"></div>
|
<div v-if="secondaryAction" class="divider"></div>
|
||||||
<div v-if="secondaryAction" class="actions" @click="handleSecondaryClick">
|
<div v-if="secondaryAction" class="actions" @click="handleSecondaryClick">
|
||||||
<p class="text">{{ secondaryAction.text }}</p>
|
<p class="text">{{ secondaryAction.text }}</p>
|
||||||
<div class="keys">
|
<div class="keys">
|
||||||
<Key v-if="(os === 'windows' || os === 'linux') && secondaryAction.showModifier" :input="'Ctrl'" />
|
<Key v-if="(os === 'windows' || os === 'linux') && secondaryAction.showModifier" :input="'Ctrl'" />
|
||||||
<IconsCmd v-if="os === 'macos' && secondaryAction.showModifier" />
|
<IconsCmd v-if="os === 'macos' && secondaryAction.showModifier" />
|
||||||
<component :is="secondaryAction.icon" :input="secondaryAction.input" />
|
<component :is="secondaryAction.icon" :input="secondaryAction.input" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { platform } from "@tauri-apps/plugin-os";
|
import { platform } from "@tauri-apps/plugin-os";
|
||||||
import IconsCmd from './Keys/Cmd.vue';
|
import IconsCmd from './Keys/Cmd.vue';
|
||||||
import Key from './Keys/Key.vue';
|
import Key from './Keys/Key.vue';
|
||||||
|
|
||||||
interface Action {
|
interface Action {
|
||||||
text: string;
|
text: string;
|
||||||
icon: any;
|
icon: any;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
showModifier?: boolean;
|
showModifier?: boolean;
|
||||||
input?: string;
|
input?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
primaryAction?: Action;
|
primaryAction?: Action;
|
||||||
secondaryAction?: Action;
|
secondaryAction?: Action;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const os = ref<string>("");
|
const os = ref<string>("");
|
||||||
|
|
||||||
const handlePrimaryClick = (event: MouseEvent) => {
|
const handlePrimaryClick = (event: MouseEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (props.primaryAction?.onClick) {
|
if (props.primaryAction?.onClick) {
|
||||||
props.primaryAction.onClick();
|
props.primaryAction.onClick();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSecondaryClick = (event: MouseEvent) => {
|
const handleSecondaryClick = (event: MouseEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (props.secondaryAction?.onClick) {
|
if (props.secondaryAction?.onClick) {
|
||||||
props.secondaryAction.onClick();
|
props.secondaryAction.onClick();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
os.value = await platform();
|
os.value = await platform();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.bottombar {
|
.bottombar {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(18px);
|
||||||
border-radius: 0 0 11px 11px;
|
border-radius: 0 0 11px 11px;
|
||||||
background-color: rgba(46, 45, 43, 0.051);
|
background-color: rgba(46, 45, 43, 0.051);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 11px;
|
padding-left: 11px;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.branding {
|
.branding {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.keys {
|
.keys {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
width: 2px;
|
width: 2px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
background-color: var(--border);
|
background-color: var(--border);
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paste,
|
.paste,
|
||||||
.actions {
|
.actions {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.paste:hover,
|
.paste:hover,
|
||||||
.actions:hover {
|
.actions:hover {
|
||||||
background-color: var(--border);
|
background-color: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.paste:active,
|
.paste:active,
|
||||||
.actions:active {
|
.actions:active {
|
||||||
background-color: var(--border-active, #444);
|
background-color: var(--border-active, #444);
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .paste:hover ~ .divider,
|
&:hover .paste:hover ~ .divider,
|
||||||
&:hover .divider:has(+ .actions:hover) {
|
&:hover .divider:has(+ .actions:hover) {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#F84E4E" fill-rule="evenodd"
|
<path fill="#F84E4E" fill-rule="evenodd"
|
||||||
d="M9 2H7a.5.5 0 0 0-.5.5V3h3v-.5A.5.5 0 0 0 9 2m2 1v-.5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2V3H2.251a.75.75 0 0 0 0 1.5h.312l.317 7.625A3 3 0 0 0 5.878 15h4.245a3 3 0 0 0 2.997-2.875l.318-7.625h.312a.75.75 0 0 0 0-1.5zm.936 1.5H4.064l.315 7.562A1.5 1.5 0 0 0 5.878 13.5h4.245a1.5 1.5 0 0 0 1.498-1.438zm-6.186 2v5a.75.75 0 0 0 1.5 0v-5a.75.75 0 0 0-1.5 0m3.75-.75a.75.75 0 0 1 .75.75v5a.75.75 0 0 1-1.5 0v-5a.75.75 0 0 1 .75-.75"
|
d="M9 2H7a.5.5 0 0 0-.5.5V3h3v-.5A.5.5 0 0 0 9 2m2 1v-.5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2V3H2.251a.75.75 0 0 0 0 1.5h.312l.317 7.625A3 3 0 0 0 5.878 15h4.245a3 3 0 0 0 2.997-2.875l.318-7.625h.312a.75.75 0 0 0 0-1.5zm.936 1.5H4.064l.315 7.562A1.5 1.5 0 0 0 5.878 13.5h4.245a1.5 1.5 0 0 0 1.498-1.438zm-6.186 2v5a.75.75 0 0 0 1.5 0v-5a.75.75 0 0 0-1.5 0m3.75-.75a.75.75 0 0 1 .75.75v5a.75.75 0 0 1-1.5 0v-5a.75.75 0 0 1 .75-.75"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M0 1.38272C0 0.619063 0.596954 0 1.33333 0C1.33333 0 2.66667 0 2.66667 0C3.40305 0 4 0.619063 4 1.38272C4 1.38272 4 1.38272 4 1.38272C4 2.14637 3.40305 2.76543 2.66667 2.76543C2.66667 2.76543 1.33333 2.76543 1.33333 2.76543C0.596954 2.76543 0 2.14637 0 1.38272"
|
d="M0 1.38272C0 0.619063 0.596954 0 1.33333 0C1.33333 0 2.66667 0 2.66667 0C3.40305 0 4 0.619063 4 1.38272C4 1.38272 4 1.38272 4 1.38272C4 2.14637 3.40305 2.76543 2.66667 2.76543C2.66667 2.76543 1.33333 2.76543 1.33333 2.76543C0.596954 2.76543 0 2.14637 0 1.38272"
|
||||||
fill="none" stroke-width="1.5" stroke="#E5DFD5" stroke-linecap="round" stroke-linejoin="round"
|
fill="none" stroke-width="1.5" stroke="#E5DFD5" stroke-linecap="round" stroke-linejoin="round"
|
||||||
transform="translate(5 0.778)" />
|
transform="translate(5 0.778)" />
|
||||||
<path
|
<path
|
||||||
d="M2.66667 0C2.66667 0 1.33333 0 1.33333 0C0.596954 0 0 0.619063 0 1.38272C0 1.38272 0 9.67901 0 9.67901C0 10.4427 0.596954 11.0617 1.33333 11.0617C1.33333 11.0617 8 11.0617 8 11.0617C8.73638 11.0617 9.33333 10.4427 9.33333 9.67901C9.33333 9.67901 9.33333 1.38272 9.33333 1.38272C9.33333 0.619063 8.73638 0 8 0C8 0 6.66667 0 6.66667 0"
|
d="M2.66667 0C2.66667 0 1.33333 0 1.33333 0C0.596954 0 0 0.619063 0 1.38272C0 1.38272 0 9.67901 0 9.67901C0 10.4427 0.596954 11.0617 1.33333 11.0617C1.33333 11.0617 8 11.0617 8 11.0617C8.73638 11.0617 9.33333 10.4427 9.33333 9.67901C9.33333 9.67901 9.33333 1.38272 9.33333 1.38272C9.33333 0.619063 8.73638 0 8 0C8 0 6.66667 0 6.66667 0"
|
||||||
fill="none" stroke-width="1.5" stroke="#E5DFD5" stroke-linecap="round" stroke-linejoin="round"
|
fill="none" stroke-width="1.5" stroke="#E5DFD5" stroke-linecap="round" stroke-linejoin="round"
|
||||||
transform="translate(2.333 2.161)" />
|
transform="translate(2.333 2.161)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M8.922 9.842q.077.425.078.896c0 1.907-1.387 3.66-3.79 3.894a4.78 4.78 0 0 1-4.208-1.774a2 2 0 0 1-.21-.333c-.231-.461-.292-1-.292-1.528c.312.047.599.045.852 0c.635-.112 1.061-.487 1.148-1C2.73 8.637 3.572 7 5.76 7q.224 0 .435.028l3.417-4.784a2.971 2.971 0 1 1 4.145 4.145zm-.56-1.444l2.819-2.013A2.7 2.7 0 0 0 9.615 4.82L7.626 7.605q.43.324.737.793m4.066-2.904l.457-.326a1.471 1.471 0 1 0-2.052-2.052l-.326.457a4.2 4.2 0 0 1 1.921 1.921M3.98 10.247c.086-.507.272-.962.54-1.264c.225-.254.572-.483 1.242-.483c.517 0 .913.197 1.198.523c.297.34.541.906.541 1.715c0 1.121-.786 2.24-2.435 2.4a3.3 3.3 0 0 1-2.63-.922c.76-.337 1.374-.965 1.544-1.969"
|
d="M8.922 9.842q.077.425.078.896c0 1.907-1.387 3.66-3.79 3.894a4.78 4.78 0 0 1-4.208-1.774a2 2 0 0 1-.21-.333c-.231-.461-.292-1-.292-1.528c.312.047.599.045.852 0c.635-.112 1.061-.487 1.148-1C2.73 8.637 3.572 7 5.76 7q.224 0 .435.028l3.417-4.784a2.971 2.971 0 1 1 4.145 4.145zm-.56-1.444l2.819-2.013A2.7 2.7 0 0 0 9.615 4.82L7.626 7.605q.43.324.737.793m4.066-2.904l.457-.326a1.471 1.471 0 1 0-2.052-2.052l-.326.457a4.2 4.2 0 0 1 1.921 1.921M3.98 10.247c.086-.507.272-.962.54-1.264c.225-.254.572-.483 1.242-.483c.517 0 .913.197 1.198.523c.297.34.541.906.541 1.715c0 1.121-.786 2.24-2.435 2.4a3.3 3.3 0 0 1-2.63-.922c.76-.337 1.374-.965 1.544-1.969"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M3.75 16.0714L11.25 16.0714C12.2855 16.0714 13.125 15.208 13.125 14.1429L13.125 8.02672C13.1248 7.5147 12.9269 7.02399 12.5748 6.66239L8.52375 2.493C8.17225 2.13169 7.69568 1.92868 7.19875 1.92857L3.75 1.92857C2.71447 1.92857 1.875 2.79202 1.875 3.85715L1.875 14.1429C1.875 15.208 2.71447 16.0714 3.75 16.0714M15 8.02672C15.0003 7.00424 14.6053 6.02271 13.9018 5.29904L9.85 1.13143C9.1465 0.406921 8.19178 -0.000123228 7.19625 0L3.75 0C1.67893 0 0 1.7269 0 3.85714L0 14.1429C2.38419e-07 16.2731 1.67893 18 3.75 18L11.25 18C13.3211 18 15 16.2731 15 14.1429L15 8.02672ZM8.40003 12.2529C8.03446 11.8764 8.03446 11.2665 8.40003 10.89L9.61253 9.64286L8.40003 8.39571C8.05583 8.01577 8.06598 7.4237 8.423 7.05648C8.78002 6.68926 9.35564 6.67882 9.72503 7.03286L11.6 8.96143C11.9656 9.33791 11.9656 9.94781 11.6 10.3243L9.72503 12.2529C9.35901 12.6289 8.76605 12.6289 8.40003 12.2529M6.60003 8.39571C6.94423 8.01577 6.93407 7.4237 6.57706 7.05649C6.22004 6.68927 5.64442 6.67882 5.27503 7.03286L3.40003 8.96143C3.03446 9.33791 3.03446 9.94781 3.40003 10.3243L5.27503 12.2529C5.50874 12.5108 5.86072 12.617 6.19289 12.5298C6.52505 12.4425 6.78443 12.1757 6.86926 11.8341C6.95409 11.4924 6.85084 11.1304 6.60003 10.89L5.38753 9.64286L6.60003 8.39571Z"
|
d="M3.75 16.0714L11.25 16.0714C12.2855 16.0714 13.125 15.208 13.125 14.1429L13.125 8.02672C13.1248 7.5147 12.9269 7.02399 12.5748 6.66239L8.52375 2.493C8.17225 2.13169 7.69568 1.92868 7.19875 1.92857L3.75 1.92857C2.71447 1.92857 1.875 2.79202 1.875 3.85715L1.875 14.1429C1.875 15.208 2.71447 16.0714 3.75 16.0714M15 8.02672C15.0003 7.00424 14.6053 6.02271 13.9018 5.29904L9.85 1.13143C9.1465 0.406921 8.19178 -0.000123228 7.19625 0L3.75 0C1.67893 0 0 1.7269 0 3.85714L0 14.1429C2.38419e-07 16.2731 1.67893 18 3.75 18L11.25 18C13.3211 18 15 16.2731 15 14.1429L15 8.02672ZM8.40003 12.2529C8.03446 11.8764 8.03446 11.2665 8.40003 10.89L9.61253 9.64286L8.40003 8.39571C8.05583 8.01577 8.06598 7.4237 8.423 7.05648C8.78002 6.68926 9.35564 6.67882 9.72503 7.03286L11.6 8.96143C11.9656 9.33791 11.9656 9.94781 11.6 10.3243L9.72503 12.2529C9.35901 12.6289 8.76605 12.6289 8.40003 12.2529M6.60003 8.39571C6.94423 8.01577 6.93407 7.4237 6.57706 7.05649C6.22004 6.68927 5.64442 6.67882 5.27503 7.03286L3.40003 8.96143C3.03446 9.33791 3.03446 9.94781 3.40003 10.3243L5.27503 12.2529C5.50874 12.5108 5.86072 12.617 6.19289 12.5298C6.52505 12.4425 6.78443 12.1757 6.86926 11.8341C6.95409 11.4924 6.85084 11.1304 6.60003 10.89L5.38753 9.64286L6.60003 8.39571Z"
|
||||||
fill="#E5DFD5"
|
fill="#E5DFD5"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
transform="translate(1.5 0)" />
|
transform="translate(1.5 0)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<g fill="none">
|
<g fill="none">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M7.47 1.22a.75.75 0 0 1 1.06 0l1.75 1.75a.75.75 0 1 1-1.06 1.06l-.47-.47v8.88l.47-.47a.75.75 0 1 1 1.06 1.06l-1.75 1.75a.75.75 0 0 1-1.06 0l-1.75-1.75a.75.75 0 1 1 1.06-1.06l.47.47V3.56l-.47.47a.75.75 0 0 1-1.06-1.06zM1.22 7.47a.75.75 0 0 0 0 1.06l1.75 1.75a.75.75 0 1 0 1.06-1.06L2.81 8l1.22-1.22a.75.75 0 0 0-1.06-1.06zm13.56 1.06l-1.75 1.75a.75.75 0 1 1-1.06-1.06L13.19 8l-1.22-1.22a.75.75 0 0 1 1.06-1.06l1.75 1.75a.75.75 0 0 1 0 1.06"
|
d="M7.47 1.22a.75.75 0 0 1 1.06 0l1.75 1.75a.75.75 0 1 1-1.06 1.06l-.47-.47v8.88l.47-.47a.75.75 0 1 1 1.06 1.06l-1.75 1.75a.75.75 0 0 1-1.06 0l-1.75-1.75a.75.75 0 1 1 1.06-1.06l.47.47V3.56l-.47.47a.75.75 0 0 1-1.06-1.06zM1.22 7.47a.75.75 0 0 0 0 1.06l1.75 1.75a.75.75 0 1 0 1.06-1.06L2.81 8l1.22-1.22a.75.75 0 0 0-1.06-1.06zm13.56 1.06l-1.75 1.75a.75.75 0 1 1-1.06-1.06L13.19 8l-1.22-1.22a.75.75 0 0 1 1.06-1.06l1.75 1.75a.75.75 0 0 1 0 1.06"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
<path stroke="#E5DFD5" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.5 8h11" />
|
<path stroke="#E5DFD5" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M2.5 8h11" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M11.25 16.0714L3.75 16.0714C2.71447 16.0714 1.875 15.208 1.875 14.1429L1.875 3.85714C1.875 2.79202 2.71447 1.92857 3.75 1.92857L6.25 1.92857L6.25 5.14286C6.25 7.2731 7.92893 9 10 9L13.125 9L13.125 14.1429C13.125 15.208 12.2855 16.0714 11.25 16.0714M12.8788 7.07143C12.7961 6.92188 12.6944 6.78437 12.5763 6.66257L8.5225 2.493C8.40408 2.37151 8.2704 2.26687 8.125 2.18186L8.125 5.14286C8.125 6.20798 8.96447 7.07143 10 7.07143L12.8788 7.07143ZM13.9013 5.29843C14.6049 6.02193 15.0001 7.00338 15 8.02672L15 14.1429C15 16.2731 13.3211 18 11.25 18L3.75 18C1.67893 18 0 16.2731 0 14.1429L0 3.85714C-5.96046e-07 1.7269 1.67893 0 3.75 0L7.19625 0C8.19116 -0.000122309 9.14535 0.406423 9.84875 1.13014L13.9013 5.29843Z"
|
d="M11.25 16.0714L3.75 16.0714C2.71447 16.0714 1.875 15.208 1.875 14.1429L1.875 3.85714C1.875 2.79202 2.71447 1.92857 3.75 1.92857L6.25 1.92857L6.25 5.14286C6.25 7.2731 7.92893 9 10 9L13.125 9L13.125 14.1429C13.125 15.208 12.2855 16.0714 11.25 16.0714M12.8788 7.07143C12.7961 6.92188 12.6944 6.78437 12.5763 6.66257L8.5225 2.493C8.40408 2.37151 8.2704 2.26687 8.125 2.18186L8.125 5.14286C8.125 6.20798 8.96447 7.07143 10 7.07143L12.8788 7.07143ZM13.9013 5.29843C14.6049 6.02193 15.0001 7.00338 15 8.02672L15 14.1429C15 16.2731 13.3211 18 11.25 18L3.75 18C1.67893 18 0 16.2731 0 14.1429L0 3.85714C-5.96046e-07 1.7269 1.67893 0 3.75 0L7.19625 0C8.19116 -0.000122309 9.14535 0.406423 9.84875 1.13014L13.9013 5.29843Z"
|
||||||
fill="#E5DFD5"
|
fill="#E5DFD5"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
transform="translate(1.5 0)" />
|
transform="translate(1.5 0)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M7.199 2H8.8a.2.2 0 0 1 .2.2c0 1.808 1.958 2.939 3.524 2.034a.2.2 0 0 1 .271.073l.802 1.388a.2.2 0 0 1-.073.272c-1.566.904-1.566 3.164 0 4.069a.2.2 0 0 1 .073.271l-.802 1.388a.2.2 0 0 1-.271.073C10.958 10.863 9 11.993 9 13.8a.2.2 0 0 1-.199.2H7.2a.2.2 0 0 1-.2-.2c0-1.808-1.958-2.938-3.524-2.034a.2.2 0 0 1-.272-.073l-.8-1.388a.2.2 0 0 1 .072-.271c1.566-.905 1.566-3.165 0-4.07a.2.2 0 0 1-.073-.27l.801-1.389a.2.2 0 0 1 .272-.072C5.042 5.138 7 4.007 7 2.199c0-.11.089-.199.199-.199M5.5 2.2c0-.94.76-1.7 1.699-1.7H8.8c.94 0 1.7.76 1.7 1.7a.85.85 0 0 0 1.274.735a1.7 1.7 0 0 1 2.32.622l.802 1.388c.469.813.19 1.851-.622 2.32a.85.85 0 0 0 0 1.472a1.7 1.7 0 0 1 .622 2.32l-.802 1.388a1.7 1.7 0 0 1-2.32.622a.85.85 0 0 0-1.274.735c0 .939-.76 1.7-1.699 1.7H7.2a1.7 1.7 0 0 1-1.699-1.7a.85.85 0 0 0-1.274-.735a1.7 1.7 0 0 1-2.32-.622l-.802-1.388a1.7 1.7 0 0 1 .622-2.32a.85.85 0 0 0 0-1.471a1.7 1.7 0 0 1-.622-2.32l.801-1.389a1.7 1.7 0 0 1 2.32-.622A.85.85 0 0 0 5.5 2.2m4 5.8a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0M11 8a3 3 0 1 1-6 0a3 3 0 0 1 6 0"
|
d="M7.199 2H8.8a.2.2 0 0 1 .2.2c0 1.808 1.958 2.939 3.524 2.034a.2.2 0 0 1 .271.073l.802 1.388a.2.2 0 0 1-.073.272c-1.566.904-1.566 3.164 0 4.069a.2.2 0 0 1 .073.271l-.802 1.388a.2.2 0 0 1-.271.073C10.958 10.863 9 11.993 9 13.8a.2.2 0 0 1-.199.2H7.2a.2.2 0 0 1-.2-.2c0-1.808-1.958-2.938-3.524-2.034a.2.2 0 0 1-.272-.073l-.8-1.388a.2.2 0 0 1 .072-.271c1.566-.905 1.566-3.165 0-4.07a.2.2 0 0 1-.073-.27l.801-1.389a.2.2 0 0 1 .272-.072C5.042 5.138 7 4.007 7 2.199c0-.11.089-.199.199-.199M5.5 2.2c0-.94.76-1.7 1.699-1.7H8.8c.94 0 1.7.76 1.7 1.7a.85.85 0 0 0 1.274.735a1.7 1.7 0 0 1 2.32.622l.802 1.388c.469.813.19 1.851-.622 2.32a.85.85 0 0 0 0 1.472a1.7 1.7 0 0 1 .622 2.32l-.802 1.388a1.7 1.7 0 0 1-2.32.622a.85.85 0 0 0-1.274.735c0 .939-.76 1.7-1.699 1.7H7.2a1.7 1.7 0 0 1-1.699-1.7a.85.85 0 0 0-1.274-.735a1.7 1.7 0 0 1-2.32-.622l-.802-1.388a1.7 1.7 0 0 1 .622-2.32a.85.85 0 0 0 0-1.471a1.7 1.7 0 0 1-.622-2.32l.801-1.389a1.7 1.7 0 0 1 2.32-.622A.85.85 0 0 0 5.5 2.2m4 5.8a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0M11 8a3 3 0 1 1-6 0a3 3 0 0 1 6 0"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M9.208 12.346c-.485 1-.953 1.154-1.208 1.154s-.723-.154-1.208-1.154c-.372-.768-.647-1.858-.749-3.187a21 21 0 0 0 3.914 0c-.102 1.329-.377 2.419-.75 3.187m.788-4.699C9.358 7.714 8.69 7.75 8 7.75s-1.358-.036-1.996-.103c.037-1.696.343-3.075.788-3.993C7.277 2.654 7.745 2.5 8 2.5s.723.154 1.208 1.154c.445.918.75 2.297.788 3.993m1.478 1.306c-.085 1.516-.375 2.848-.836 3.874a5.5 5.5 0 0 0 2.843-4.364c-.621.199-1.295.364-2.007.49m1.918-2.043c-.572.204-1.21.379-1.901.514c-.056-1.671-.354-3.14-.853-4.251a5.5 5.5 0 0 1 2.754 3.737m-8.883.514c.056-1.671.354-3.14.853-4.251A5.5 5.5 0 0 0 2.608 6.91c.572.204 1.21.379 1.901.514M2.52 8.463a5.5 5.5 0 0 0 2.843 4.364c-.46-1.026-.75-2.358-.836-3.874a15.5 15.5 0 0 1-2.007-.49M15 8A7 7 0 1 0 1 8a7 7 0 0 0 14 0"
|
d="M9.208 12.346c-.485 1-.953 1.154-1.208 1.154s-.723-.154-1.208-1.154c-.372-.768-.647-1.858-.749-3.187a21 21 0 0 0 3.914 0c-.102 1.329-.377 2.419-.75 3.187m.788-4.699C9.358 7.714 8.69 7.75 8 7.75s-1.358-.036-1.996-.103c.037-1.696.343-3.075.788-3.993C7.277 2.654 7.745 2.5 8 2.5s.723.154 1.208 1.154c.445.918.75 2.297.788 3.993m1.478 1.306c-.085 1.516-.375 2.848-.836 3.874a5.5 5.5 0 0 0 2.843-4.364c-.621.199-1.295.364-2.007.49m1.918-2.043c-.572.204-1.21.379-1.901.514c-.056-1.671-.354-3.14-.853-4.251a5.5 5.5 0 0 1 2.754 3.737m-8.883.514c.056-1.671.354-3.14.853-4.251A5.5 5.5 0 0 0 2.608 6.91c.572.204 1.21.379 1.901.514M2.52 8.463a5.5 5.5 0 0 0 2.843 4.364c-.46-1.026-.75-2.358-.836-3.874a15.5 15.5 0 0 1-2.007-.49M15 8A7 7 0 1 0 1 8a7 7 0 0 0 14 0"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M13.8462 2.07692L4.15385 2.07692C3.00679 2.07692 2.07692 3.00679 2.07692 4.15385L2.07692 11.1143L3.40892 10.1451C4.26934 9.51991 5.43685 9.5289 6.28754 10.1672L7.57246 11.1309L10.8512 8.32016C11.7843 7.52111 13.1676 7.54669 14.0705 8.37969L15.9231 10.0897L15.9231 4.15385C15.9231 3.00679 14.9932 2.07692 13.8462 2.07692M18 12.4588L18 4.15385C18 1.85974 16.1403 0 13.8462 0L4.15385 0C1.85974 0 0 1.85974 0 4.15385L0 13.8462C3.30118e-07 16.1403 1.85974 18 4.15385 18L13.8462 18C16.1403 18 18 16.1403 18 13.8462L18 12.4588ZM15.9231 12.9157L12.6623 9.90554C12.5333 9.78671 12.3358 9.78314 12.2026 9.89723L8.29108 13.2508L7.65831 13.7935L6.99231 13.2937L5.04 11.8302C4.91867 11.7398 4.75269 11.7386 4.63015 11.8274L2.07692 13.6814L2.07692 13.8462C2.07692 14.9932 3.00679 15.9231 4.15385 15.9231L13.8462 15.9231C14.9932 15.9231 15.9231 14.9932 15.9231 13.8462L15.9231 12.9157ZM8.30769 6.23077C8.30769 7.37782 7.37782 8.30769 6.23077 8.30769C5.08372 8.30769 4.15385 7.37782 4.15385 6.23077C4.15385 5.08372 5.08372 4.15385 6.23077 4.15385C7.37782 4.15385 8.30769 5.08372 8.30769 6.23077"
|
d="M13.8462 2.07692L4.15385 2.07692C3.00679 2.07692 2.07692 3.00679 2.07692 4.15385L2.07692 11.1143L3.40892 10.1451C4.26934 9.51991 5.43685 9.5289 6.28754 10.1672L7.57246 11.1309L10.8512 8.32016C11.7843 7.52111 13.1676 7.54669 14.0705 8.37969L15.9231 10.0897L15.9231 4.15385C15.9231 3.00679 14.9932 2.07692 13.8462 2.07692M18 12.4588L18 4.15385C18 1.85974 16.1403 0 13.8462 0L4.15385 0C1.85974 0 0 1.85974 0 4.15385L0 13.8462C3.30118e-07 16.1403 1.85974 18 4.15385 18L13.8462 18C16.1403 18 18 16.1403 18 13.8462L18 12.4588ZM15.9231 12.9157L12.6623 9.90554C12.5333 9.78671 12.3358 9.78314 12.2026 9.89723L8.29108 13.2508L7.65831 13.7935L6.99231 13.2937L5.04 11.8302C4.91867 11.7398 4.75269 11.7386 4.63015 11.8274L2.07692 13.6814L2.07692 13.8462C2.07692 14.9932 3.00679 15.9231 4.15385 15.9231L13.8462 15.9231C14.9932 15.9231 15.9231 14.9932 15.9231 13.8462L15.9231 12.9157ZM8.30769 6.23077C8.30769 7.37782 7.37782 8.30769 6.23077 8.30769C5.08372 8.30769 4.15385 7.37782 4.15385 6.23077C4.15385 5.08372 5.08372 4.15385 6.23077 4.15385C7.37782 4.15385 8.30769 5.08372 8.30769 6.23077"
|
||||||
fill="#E5DFD5"
|
fill="#E5DFD5"
|
||||||
fill-rule="evenodd" />
|
fill-rule="evenodd" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M2.68978 6.95235C3.0999 6.55662 3.75151 6.56259 4.1543 6.96577C4.5571 7.36895 4.56246 8.02056 4.16634 8.43031C4.16634 8.43031 3.15364 9.443 3.15364 9.443C1.68651 10.9393 1.69832 13.3381 3.18012 14.8199C4.66192 16.3017 7.06072 16.3135 8.55703 14.8464C8.55703 14.8464 9.56973 13.8337 9.56973 13.8337C9.98137 13.4501 10.6228 13.4614 11.0207 13.8593C11.4185 14.2571 11.4299 14.8986 11.0463 15.3102C11.0463 15.3102 10.035 16.3229 10.035 16.3229C7.71847 18.5799 4.01808 18.5559 1.73113 16.2689C-0.555826 13.982 -0.579909 10.2816 1.67708 7.96504C1.67708 7.96504 2.68978 6.95235 2.68978 6.95235ZM13.8337 9.56973C13.4501 9.98138 13.4614 10.6228 13.8593 11.0207C14.2571 11.4185 14.8986 11.4299 15.3103 11.0463C15.3103 11.0463 16.323 10.035 16.323 10.035C18.58 7.71847 18.5559 4.01808 16.2689 1.73113C13.982 -0.555826 10.2816 -0.579908 7.96505 1.67708C7.96505 1.67708 6.95235 2.68978 6.95235 2.68978C6.55662 3.0999 6.56259 3.75151 6.96577 4.15431C7.36895 4.55711 8.02056 4.56247 8.43031 4.16635C8.43031 4.16635 9.44301 3.15365 9.44301 3.15365C10.9393 1.68652 13.3381 1.69833 14.8199 3.18013C16.3017 4.66192 16.3135 7.06073 14.8464 8.55704C14.8464 8.55704 13.8337 9.56973 13.8337 9.56973ZM12.5242 6.9523C12.8038 6.69186 12.9188 6.29961 12.8243 5.92945C12.7297 5.55928 12.4407 5.27024 12.0705 5.1757C11.7004 5.08117 11.3081 5.19623 11.0477 5.47574C11.0477 5.47574 5.47574 11.0477 5.47574 11.0477C5.19623 11.3081 5.08117 11.7004 5.1757 12.0705C5.27024 12.4407 5.55928 12.7297 5.92945 12.8243C6.29961 12.9188 6.69186 12.8037 6.9523 12.5242C6.9523 12.5242 12.5242 6.9523 12.5242 6.9523Z"
|
d="M2.68978 6.95235C3.0999 6.55662 3.75151 6.56259 4.1543 6.96577C4.5571 7.36895 4.56246 8.02056 4.16634 8.43031C4.16634 8.43031 3.15364 9.443 3.15364 9.443C1.68651 10.9393 1.69832 13.3381 3.18012 14.8199C4.66192 16.3017 7.06072 16.3135 8.55703 14.8464C8.55703 14.8464 9.56973 13.8337 9.56973 13.8337C9.98137 13.4501 10.6228 13.4614 11.0207 13.8593C11.4185 14.2571 11.4299 14.8986 11.0463 15.3102C11.0463 15.3102 10.035 16.3229 10.035 16.3229C7.71847 18.5799 4.01808 18.5559 1.73113 16.2689C-0.555826 13.982 -0.579909 10.2816 1.67708 7.96504C1.67708 7.96504 2.68978 6.95235 2.68978 6.95235ZM13.8337 9.56973C13.4501 9.98138 13.4614 10.6228 13.8593 11.0207C14.2571 11.4185 14.8986 11.4299 15.3103 11.0463C15.3103 11.0463 16.323 10.035 16.323 10.035C18.58 7.71847 18.5559 4.01808 16.2689 1.73113C13.982 -0.555826 10.2816 -0.579908 7.96505 1.67708C7.96505 1.67708 6.95235 2.68978 6.95235 2.68978C6.55662 3.0999 6.56259 3.75151 6.96577 4.15431C7.36895 4.55711 8.02056 4.56247 8.43031 4.16635C8.43031 4.16635 9.44301 3.15365 9.44301 3.15365C10.9393 1.68652 13.3381 1.69833 14.8199 3.18013C16.3017 4.66192 16.3135 7.06073 14.8464 8.55704C14.8464 8.55704 13.8337 9.56973 13.8337 9.56973ZM12.5242 6.9523C12.8038 6.69186 12.9188 6.29961 12.8243 5.92945C12.7297 5.55928 12.4407 5.27024 12.0705 5.1757C11.7004 5.08117 11.3081 5.19623 11.0477 5.47574C11.0477 5.47574 5.47574 11.0477 5.47574 11.0477C5.19623 11.3081 5.08117 11.7004 5.1757 12.0705C5.27024 12.4407 5.55928 12.7297 5.92945 12.8243C6.29961 12.9188 6.69186 12.8037 6.9523 12.5242C6.9523 12.5242 12.5242 6.9523 12.5242 6.9523Z"
|
||||||
fill="#E5DFD5"
|
fill="#E5DFD5"
|
||||||
fill-rule="evenodd" />
|
fill-rule="evenodd" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M10 1.5A.75.75 0 0 0 10 3h1.94L6.97 7.97a.75.75 0 0 0 1.06 1.06L13 4.06V6a.75.75 0 0 0 1.5 0V2.25a.75.75 0 0 0-.75-.75zM7.5 3.25a.75.75 0 0 0-.75-.75H4.5a3 3 0 0 0-3 3v6a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3V9.25a.75.75 0 0 0-1.5 0v2.25a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 3 11.5v-6A1.5 1.5 0 0 1 4.5 4h2.25a.75.75 0 0 0 .75-.75"
|
d="M10 1.5A.75.75 0 0 0 10 3h1.94L6.97 7.97a.75.75 0 0 0 1.06 1.06L13 4.06V6a.75.75 0 0 0 1.5 0V2.25a.75.75 0 0 0-.75-.75zM7.5 3.25a.75.75 0 0 0-.75-.75H4.5a3 3 0 0 0-3 3v6a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3V9.25a.75.75 0 0 0-1.5 0v2.25a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 3 11.5v-6A1.5 1.5 0 0 1 4.5 4h2.25a.75.75 0 0 0 .75-.75"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M11.423 1A3.577 3.577 0 0 1 15 4.577c0 .27-.108.53-.3.722l-.528.529l-1.971 1.971l-5.059 5.059a3 3 0 0 1-1.533.82l-2.638.528a1 1 0 0 1-1.177-1.177l.528-2.638a3 3 0 0 1 .82-1.533l5.059-5.059l2.5-2.5c.191-.191.451-.299.722-.299m-2.31 4.009l-4.91 4.91a1.5 1.5 0 0 0-.41.766l-.38 1.903l1.902-.38a1.5 1.5 0 0 0 .767-.41l4.91-4.91a2.08 2.08 0 0 0-1.88-1.88m3.098.658a3.6 3.6 0 0 0-1.878-1.879l1.28-1.28c.995.09 1.788.884 1.878 1.88z"
|
d="M11.423 1A3.577 3.577 0 0 1 15 4.577c0 .27-.108.53-.3.722l-.528.529l-1.971 1.971l-5.059 5.059a3 3 0 0 1-1.533.82l-2.638.528a1 1 0 0 1-1.177-1.177l.528-2.638a3 3 0 0 1 .82-1.533l5.059-5.059l2.5-2.5c.191-.191.451-.299.722-.299m-2.31 4.009l-4.91 4.91a1.5 1.5 0 0 0-.41.766l-.38 1.903l1.902-.38a1.5 1.5 0 0 0 .767-.41l4.91-4.91a2.08 2.08 0 0 0-1.88-1.88m3.098.658a3.6 3.6 0 0 0-1.878-1.879l1.28-1.28c.995.09 1.788.884 1.878 1.88z"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M8 1.5a6.5 6.5 0 1 0 6.445 7.348a.75.75 0 1 0-1.487-.194A5.001 5.001 0 1 1 11.57 4.5h-1.32a.75.75 0 0 0 0 1.5h3a.75.75 0 0 0 .75-.75v-3a.75.75 0 0 0-1.5 0v1.06A6.48 6.48 0 0 0 8 1.5"
|
d="M8 1.5a6.5 6.5 0 1 0 6.445 7.348a.75.75 0 1 0-1.487-.194A5.001 5.001 0 1 1 11.57 4.5h-1.32a.75.75 0 0 0 0 1.5h3a.75.75 0 0 0 .75-.75v-3a.75.75 0 0 0-1.5 0v1.06A6.48 6.48 0 0 0 8 1.5"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M3.279 2.544A.75.75 0 0 1 4 2h8a.75.75 0 0 1 .721.544l.5 1.75a.75.75 0 1 1-1.442.412L11.434 3.5H8.75l-.004 9H9.5a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5h.746l.004-9H4.566L4.22 4.706a.75.75 0 1 1-1.442-.412z"
|
d="M3.279 2.544A.75.75 0 0 1 4 2h8a.75.75 0 0 1 .721.544l.5 1.75a.75.75 0 1 1-1.442.412L11.434 3.5H8.75l-.004 9H9.5a.75.75 0 0 1 0 1.5h-3a.75.75 0 0 1 0-1.5h.746l.004-9H4.566L4.22 4.706a.75.75 0 1 1-1.442-.412z"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M3.75 16.0714L11.25 16.0714C12.2855 16.0714 13.125 15.208 13.125 14.1429L13.125 8.02672C13.1248 7.5147 12.9269 7.02399 12.5748 6.66239L8.52375 2.493C8.17225 2.13169 7.69568 1.92868 7.19875 1.92857L3.75 1.92857C2.71447 1.92857 1.875 2.79202 1.875 3.85714L1.875 14.1429C1.875 15.208 2.71447 16.0714 3.75 16.0714M15 8.02672C15.0003 7.00424 14.6053 6.02271 13.9018 5.29904L9.85 1.13143C9.1465 0.406921 8.19178 -0.000123228 7.19625 0L3.75 0C1.67893 0 0 1.7269 0 3.85714L0 14.1429C2.38419e-07 16.2731 1.67893 18 3.75 18L11.25 18C13.3211 18 15 16.2731 15 14.1429L15 8.02672ZM3.75 9.32143C3.75 8.78887 4.16973 8.35714 4.6875 8.35714L10.3125 8.35714C10.8303 8.35714 11.25 8.78887 11.25 9.32143C11.25 9.85399 10.8303 10.2857 10.3125 10.2857L4.6875 10.2857C4.16973 10.2857 3.75 9.85399 3.75 9.32143M4.6875 12.2143C4.35256 12.2143 4.04307 12.3981 3.8756 12.6964C3.70813 12.9948 3.70813 13.3624 3.8756 13.6607C4.04307 13.9591 4.35256 14.1429 4.6875 14.1429L7.8125 14.1429C8.14744 14.1429 8.45693 13.9591 8.6244 13.6607C8.79187 13.3624 8.79187 12.9948 8.6244 12.6964C8.45693 12.3981 8.14744 12.2143 7.8125 12.2143L4.6875 12.2143Z"
|
d="M3.75 16.0714L11.25 16.0714C12.2855 16.0714 13.125 15.208 13.125 14.1429L13.125 8.02672C13.1248 7.5147 12.9269 7.02399 12.5748 6.66239L8.52375 2.493C8.17225 2.13169 7.69568 1.92868 7.19875 1.92857L3.75 1.92857C2.71447 1.92857 1.875 2.79202 1.875 3.85714L1.875 14.1429C1.875 15.208 2.71447 16.0714 3.75 16.0714M15 8.02672C15.0003 7.00424 14.6053 6.02271 13.9018 5.29904L9.85 1.13143C9.1465 0.406921 8.19178 -0.000123228 7.19625 0L3.75 0C1.67893 0 0 1.7269 0 3.85714L0 14.1429C2.38419e-07 16.2731 1.67893 18 3.75 18L11.25 18C13.3211 18 15 16.2731 15 14.1429L15 8.02672ZM3.75 9.32143C3.75 8.78887 4.16973 8.35714 4.6875 8.35714L10.3125 8.35714C10.8303 8.35714 11.25 8.78887 11.25 9.32143C11.25 9.85399 10.8303 10.2857 10.3125 10.2857L4.6875 10.2857C4.16973 10.2857 3.75 9.85399 3.75 9.32143M4.6875 12.2143C4.35256 12.2143 4.04307 12.3981 3.8756 12.6964C3.70813 12.9948 3.70813 13.3624 3.8756 13.6607C4.04307 13.9591 4.35256 14.1429 4.6875 14.1429L7.8125 14.1429C8.14744 14.1429 8.45693 13.9591 8.6244 13.6607C8.79187 13.3624 8.79187 12.9948 8.6244 12.6964C8.45693 12.3981 8.14744 12.2143 7.8125 12.2143L4.6875 12.2143Z"
|
||||||
fill="#E5DFD5"
|
fill="#E5DFD5"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
transform="translate(1.5 0)" />
|
transform="translate(1.5 0)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 16 16">
|
||||||
<path fill="#E5DFD5" fill-rule="evenodd"
|
<path fill="#E5DFD5" fill-rule="evenodd"
|
||||||
d="M11 13.5H5A1.5 1.5 0 0 1 3.5 12V4A1.5 1.5 0 0 1 5 2.5h.5v.75c0 .414.336.75.75.75H7v2h-.75a.75.75 0 0 0-.75.75v.5c0 .414.336.75.75.75H7v2h-.75a.75.75 0 0 0-.75.75v.5c0 .414.336.75.75.75H7v-2h.75a.75.75 0 0 0 .75-.75v-.5A.75.75 0 0 0 7.75 8H7V6h.75a.75.75 0 0 0 .75-.75v-.5A.75.75 0 0 0 7.75 4H7V2.5h.757a1.5 1.5 0 0 1 1.061.44l3.243 3.242a1.5 1.5 0 0 1 .439 1.06V12a1.5 1.5 0 0 1-1.5 1.5m2.121-8.379A3 3 0 0 1 14 7.243V12a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V4a3 3 0 0 1 3-3h2.757a3 3 0 0 1 2.122.879z"
|
d="M11 13.5H5A1.5 1.5 0 0 1 3.5 12V4A1.5 1.5 0 0 1 5 2.5h.5v.75c0 .414.336.75.75.75H7v2h-.75a.75.75 0 0 0-.75.75v.5c0 .414.336.75.75.75H7v2h-.75a.75.75 0 0 0-.75.75v.5c0 .414.336.75.75.75H7v-2h.75a.75.75 0 0 0 .75-.75v-.5A.75.75 0 0 0 7.75 8H7V6h.75a.75.75 0 0 0 .75-.75v-.5A.75.75 0 0 0 7.75 4H7V2.5h.757a1.5 1.5 0 0 1 1.061.44l3.243 3.242a1.5 1.5 0 0 1 .439 1.06V12a1.5 1.5 0 0 1-1.5 1.5m2.121-8.379A3 3 0 0 1 14 7.243V12a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V4a3 3 0 0 1 3-3h2.757a3 3 0 0 1 2.122.879z"
|
||||||
clip-rule="evenodd" />
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,39 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="24px"
|
width="24px"
|
||||||
height="20px"
|
height="20px"
|
||||||
viewBox="0 0 24 20"
|
viewBox="0 0 24 20"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<path d="M0 0L24 0L24 20L0 20L0 0Z" id="path_1" />
|
<path d="M0 0L24 0L24 20L0 20L0 0Z" id="path_1" />
|
||||||
<clipPath id="clip_1">
|
<clipPath id="clip_1">
|
||||||
<use
|
<use
|
||||||
xlink:href="#path_1"
|
xlink:href="#path_1"
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
transform="translate(0, -2.133523)" />
|
transform="translate(0, -2.133523)" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
<g id="cmd">
|
<g id="cmd">
|
||||||
<path
|
<path
|
||||||
d="M-751 -2016L-751 -2016L-751 -1996L-775 -1996L-775 -2016L-751 -2016Z"
|
d="M-751 -2016L-751 -2016L-751 -1996L-775 -1996L-775 -2016L-751 -2016Z"
|
||||||
id="cmd"
|
id="cmd"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="none" />
|
stroke="none" />
|
||||||
<path
|
<path
|
||||||
d="M12 0L17.6 0C19.8402 0 20.9603 0 21.816 0.435974Q22.3804 0.723593 22.8284 1.17157Q23.2764 1.61955 23.564 2.18404C24 3.03969 24 4.15979 24 6.4L24 13.6C24 15.8402 24 16.9603 23.564 17.816Q23.2764 18.3804 22.8284 18.8284Q22.3804 19.2764 21.816 19.564C20.9603 20 19.8402 20 17.6 20L6.4 20C4.15979 20 3.03969 20 2.18404 19.564Q1.61955 19.2764 1.17157 18.8284Q0.723594 18.3804 0.435974 17.816C0 16.9603 0 15.8402 0 13.6L0 6.4C0 4.15979 0 3.03969 0.435974 2.18404Q0.723594 1.61955 1.17157 1.17157Q1.61955 0.723594 2.18404 0.435974C3.03969 0 4.15979 0 6.4 0L12 0Z"
|
d="M12 0L17.6 0C19.8402 0 20.9603 0 21.816 0.435974Q22.3804 0.723593 22.8284 1.17157Q23.2764 1.61955 23.564 2.18404C24 3.03969 24 4.15979 24 6.4L24 13.6C24 15.8402 24 16.9603 23.564 17.816Q23.2764 18.3804 22.8284 18.8284Q22.3804 19.2764 21.816 19.564C20.9603 20 19.8402 20 17.6 20L6.4 20C4.15979 20 3.03969 20 2.18404 19.564Q1.61955 19.2764 1.17157 18.8284Q0.723594 18.3804 0.435974 17.816C0 16.9603 0 15.8402 0 13.6L0 6.4C0 4.15979 0 3.03969 0.435974 2.18404Q0.723594 1.61955 1.17157 1.17157Q1.61955 0.723594 2.18404 0.435974C3.03969 0 4.15979 0 6.4 0L12 0Z"
|
||||||
id="Rectangle"
|
id="Rectangle"
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
fill-opacity="0.050980393"
|
fill-opacity="0.050980393"
|
||||||
stroke="none" />
|
stroke="none" />
|
||||||
<g id="⌘" clip-path="url(#clip_1)" transform="translate(0 2.133523)">
|
<g id="⌘" clip-path="url(#clip_1)" transform="translate(0 2.133523)">
|
||||||
<g transform="translate(5.5692472, 0)" id="⌘" fill="#E5DFD5">
|
<g transform="translate(5.5692472, 0)" id="⌘" fill="#E5DFD5">
|
||||||
<path
|
<path
|
||||||
d="M3.55007 12.8061Q2.98224 12.8061 2.51598 12.5268Q2.04972 12.2475 1.77042 11.7789Q1.49112 11.3104 1.49112 10.7472Q1.49112 10.1747 1.77042 9.70614Q2.04972 9.23757 2.51598 8.95827Q2.98224 8.67898 3.55007 8.67898L4.5657 8.67898L4.5657 7.04474L3.55007 7.04474Q2.98224 7.04474 2.51598 6.76775Q2.04972 6.49077 1.77042 6.02219Q1.49112 5.55362 1.49112 4.98579Q1.49112 4.41797 1.77042 3.9494Q2.04972 3.48082 2.51598 3.20383Q2.98224 2.92685 3.55007 2.92684Q4.1179 2.92684 4.58647 3.20383Q5.05504 3.48082 5.33434 3.9494Q5.61364 4.41797 5.61364 4.98579L5.61364 5.99219L7.25249 5.99219L7.25249 4.98579Q7.25249 4.41797 7.52947 3.9494Q7.80646 3.48082 8.27504 3.20383Q8.74361 2.92685 9.31144 2.92684Q9.87926 2.92684 10.3455 3.20383Q10.8118 3.48082 11.0888 3.9494Q11.3658 4.41797 11.3658 4.98579Q11.3658 5.55362 11.0888 6.02219Q10.8118 6.49077 10.3455 6.76775Q9.87926 7.04474 9.31144 7.04474L8.30043 7.04474L8.30043 8.67898L9.31144 8.67898Q9.87926 8.67898 10.3455 8.95827Q10.8118 9.23757 11.0888 9.70614Q11.3658 10.1747 11.3658 10.7472Q11.3658 11.3104 11.0888 11.7789Q10.8118 12.2475 10.3455 12.5268Q9.87926 12.8061 9.31144 12.8061Q8.74361 12.8061 8.27504 12.5268Q7.80646 12.2475 7.52947 11.7789Q7.25249 11.3104 7.25249 10.7472L7.25249 9.73153L5.61364 9.73153L5.61364 10.7472Q5.61364 11.3104 5.33434 11.7789Q5.05504 12.2475 4.58647 12.5268Q4.1179 12.8061 3.55007 12.8061ZM3.55007 11.7536Q3.97017 11.7536 4.26563 11.4604Q4.56108 11.1673 4.5657 10.7472L4.5657 9.73153L3.55007 9.73153Q3.12997 9.73615 2.83452 10.0293Q2.53906 10.3224 2.53906 10.7472Q2.53906 11.1673 2.83452 11.4604Q3.12997 11.7536 3.55007 11.7536ZM9.31144 11.7536Q9.72692 11.7536 10.0224 11.4604Q10.3178 11.1673 10.3178 10.7472Q10.3178 10.3224 10.0224 10.0293Q9.72692 9.73615 9.31144 9.73153L8.30043 9.73153L8.30043 10.7472Q8.29581 11.1673 8.59357 11.4604Q8.89133 11.7536 9.31144 11.7536ZM3.55007 5.99219L4.5657 5.99219L4.5657 4.98579Q4.56108 4.56569 4.26563 4.27255Q3.97017 3.9794 3.55007 3.9794Q3.12997 3.9794 2.83452 4.27255Q2.53906 4.56569 2.53906 4.98579Q2.53906 5.40589 2.83452 5.70135Q3.12997 5.9968 3.55007 5.99219ZM8.30043 5.99219L9.31144 5.99219Q9.72692 5.9968 10.0224 5.70135Q10.3178 5.40589 10.3178 4.98579Q10.3178 4.56569 10.0224 4.27255Q9.72692 3.9794 9.31144 3.9794Q8.89133 3.9794 8.59357 4.27255Q8.29581 4.56569 8.30043 4.98579L8.30043 5.99219ZM5.61364 8.67898L7.25249 8.67898L7.25249 7.04474L5.61364 7.04474L5.61364 8.67898Z" />
|
d="M3.55007 12.8061Q2.98224 12.8061 2.51598 12.5268Q2.04972 12.2475 1.77042 11.7789Q1.49112 11.3104 1.49112 10.7472Q1.49112 10.1747 1.77042 9.70614Q2.04972 9.23757 2.51598 8.95827Q2.98224 8.67898 3.55007 8.67898L4.5657 8.67898L4.5657 7.04474L3.55007 7.04474Q2.98224 7.04474 2.51598 6.76775Q2.04972 6.49077 1.77042 6.02219Q1.49112 5.55362 1.49112 4.98579Q1.49112 4.41797 1.77042 3.9494Q2.04972 3.48082 2.51598 3.20383Q2.98224 2.92685 3.55007 2.92684Q4.1179 2.92684 4.58647 3.20383Q5.05504 3.48082 5.33434 3.9494Q5.61364 4.41797 5.61364 4.98579L5.61364 5.99219L7.25249 5.99219L7.25249 4.98579Q7.25249 4.41797 7.52947 3.9494Q7.80646 3.48082 8.27504 3.20383Q8.74361 2.92685 9.31144 2.92684Q9.87926 2.92684 10.3455 3.20383Q10.8118 3.48082 11.0888 3.9494Q11.3658 4.41797 11.3658 4.98579Q11.3658 5.55362 11.0888 6.02219Q10.8118 6.49077 10.3455 6.76775Q9.87926 7.04474 9.31144 7.04474L8.30043 7.04474L8.30043 8.67898L9.31144 8.67898Q9.87926 8.67898 10.3455 8.95827Q10.8118 9.23757 11.0888 9.70614Q11.3658 10.1747 11.3658 10.7472Q11.3658 11.3104 11.0888 11.7789Q10.8118 12.2475 10.3455 12.5268Q9.87926 12.8061 9.31144 12.8061Q8.74361 12.8061 8.27504 12.5268Q7.80646 12.2475 7.52947 11.7789Q7.25249 11.3104 7.25249 10.7472L7.25249 9.73153L5.61364 9.73153L5.61364 10.7472Q5.61364 11.3104 5.33434 11.7789Q5.05504 12.2475 4.58647 12.5268Q4.1179 12.8061 3.55007 12.8061ZM3.55007 11.7536Q3.97017 11.7536 4.26563 11.4604Q4.56108 11.1673 4.5657 10.7472L4.5657 9.73153L3.55007 9.73153Q3.12997 9.73615 2.83452 10.0293Q2.53906 10.3224 2.53906 10.7472Q2.53906 11.1673 2.83452 11.4604Q3.12997 11.7536 3.55007 11.7536ZM9.31144 11.7536Q9.72692 11.7536 10.0224 11.4604Q10.3178 11.1673 10.3178 10.7472Q10.3178 10.3224 10.0224 10.0293Q9.72692 9.73615 9.31144 9.73153L8.30043 9.73153L8.30043 10.7472Q8.29581 11.1673 8.59357 11.4604Q8.89133 11.7536 9.31144 11.7536ZM3.55007 5.99219L4.5657 5.99219L4.5657 4.98579Q4.56108 4.56569 4.26563 4.27255Q3.97017 3.9794 3.55007 3.9794Q3.12997 3.9794 2.83452 4.27255Q2.53906 4.56569 2.53906 4.98579Q2.53906 5.40589 2.83452 5.70135Q3.12997 5.9968 3.55007 5.99219ZM8.30043 5.99219L9.31144 5.99219Q9.72692 5.9968 10.0224 5.70135Q10.3178 5.40589 10.3178 4.98579Q10.3178 4.56569 10.0224 4.27255Q9.72692 3.9794 9.31144 3.9794Q8.89133 3.9794 8.59357 4.27255Q8.29581 4.56569 8.30043 4.98579L8.30043 5.99219ZM5.61364 8.67898L7.25249 8.67898L7.25249 7.04474L5.61364 7.04474L5.61364 8.67898Z" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
<template>
|
<template>
|
||||||
<svg
|
<svg
|
||||||
width="24px"
|
width="24px"
|
||||||
height="20px"
|
height="20px"
|
||||||
viewBox="0 0 24 20"
|
viewBox="0 0 24 20"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g id="Enter" fill-opacity="1">
|
<g id="Enter" fill-opacity="1">
|
||||||
<path
|
<path
|
||||||
d="M-659 -2016L-659 -2016L-659 -1996L-683 -1996L-683 -2016L-659 -2016Z"
|
d="M-659 -2016L-659 -2016L-659 -1996L-683 -1996L-683 -2016L-659 -2016Z"
|
||||||
id="Enter"
|
id="Enter"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="none" />
|
stroke="none" />
|
||||||
<path
|
<path
|
||||||
d="M12 0L17.6 0C19.8402 0 20.9603 0 21.816 0.435974Q22.3804 0.723593 22.8284 1.17157Q23.2764 1.61955 23.564 2.18404C24 3.03969 24 4.15979 24 6.4L24 13.6C24 15.8402 24 16.9603 23.564 17.816Q23.2764 18.3804 22.8284 18.8284Q22.3804 19.2764 21.816 19.564C20.9603 20 19.8402 20 17.6 20L6.4 20C4.15979 20 3.03969 20 2.18404 19.564Q1.61955 19.2764 1.17157 18.8284Q0.723594 18.3804 0.435974 17.816C0 16.9603 0 15.8402 0 13.6L0 6.4C0 4.15979 0 3.03969 0.435974 2.18404Q0.723594 1.61955 1.17157 1.17157Q1.61955 0.723594 2.18404 0.435974C3.03969 0 4.15979 0 6.4 0L12 0Z"
|
d="M12 0L17.6 0C19.8402 0 20.9603 0 21.816 0.435974Q22.3804 0.723593 22.8284 1.17157Q23.2764 1.61955 23.564 2.18404C24 3.03969 24 4.15979 24 6.4L24 13.6C24 15.8402 24 16.9603 23.564 17.816Q23.2764 18.3804 22.8284 18.8284Q22.3804 19.2764 21.816 19.564C20.9603 20 19.8402 20 17.6 20L6.4 20C4.15979 20 3.03969 20 2.18404 19.564Q1.61955 19.2764 1.17157 18.8284Q0.723594 18.3804 0.435974 17.816C0 16.9603 0 15.8402 0 13.6L0 6.4C0 4.15979 0 3.03969 0.435974 2.18404Q0.723594 1.61955 1.17157 1.17157Q1.61955 0.723594 2.18404 0.435974C3.03969 0 4.15979 0 6.4 0L12 0Z"
|
||||||
id="Rectangle"
|
id="Rectangle"
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
fill-opacity="0.050980393"
|
fill-opacity="0.050980393"
|
||||||
stroke="none" />
|
stroke="none" />
|
||||||
<path
|
<path
|
||||||
d="M16.0597 5.48914L16.0597 10.5L7.5 10.5"
|
d="M16.0597 5.48914L16.0597 10.5L7.5 10.5"
|
||||||
id="Vector"
|
id="Vector"
|
||||||
fill="none"
|
fill="none"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
stroke="#E5DFD5"
|
stroke="#E5DFD5"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round" />
|
||||||
<path
|
<path
|
||||||
d="M9.5 8.5L9.5 12.5035L7 10.5L9.5 8.5Z"
|
d="M9.5 8.5L9.5 12.5035L7 10.5L9.5 8.5Z"
|
||||||
id="Vector"
|
id="Vector"
|
||||||
fill="#E5DFD5"
|
fill="#E5DFD5"
|
||||||
fill-rule="evenodd"
|
fill-rule="evenodd"
|
||||||
stroke="#E5DFD5"
|
stroke="#E5DFD5"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round" />
|
stroke-linejoin="round" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="key-container"
|
class="key-container"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundColor: 'var(--border)',
|
backgroundColor: 'var(--border)',
|
||||||
padding: '0 7px',
|
padding: '0 7px',
|
||||||
height: '20px',
|
height: '20px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
minWidth: '22px'
|
minWidth: '22px'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
:style="{
|
:style="{
|
||||||
color: '#E5E0D5',
|
color: '#E5E0D5',
|
||||||
fontSize: '12px'
|
fontSize: '12px'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ input }}
|
{{ input }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps<{
|
||||||
input: string;
|
input: string;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M12 0L17.6 0C19.8402 0 20.9603 0 21.816 0.435974Q22.3804 0.723593 22.8284 1.17157Q23.2764 1.61955 23.564 2.18404C24 3.03969 24 4.15979 24 6.4L24 13.6C24 15.8402 24 16.9603 23.564 17.816Q23.2764 18.3804 22.8284 18.8284Q22.3804 19.2764 21.816 19.564C20.9603 20 19.8402 20 17.6 20L6.4 20C4.15979 20 3.03969 20 2.18404 19.564Q1.61955 19.2764 1.17157 18.8284Q0.723594 18.3804 0.435974 17.816C0 16.9603 0 15.8402 0 13.6L0 6.4C0 4.15979 0 3.03969 0.435974 2.18404Q0.723594 1.61955 1.17157 1.17157Q1.61955 0.723594 2.18404 0.435974C3.03969 0 4.15979 0 6.4 0L12 0Z"
|
d="M12 0L17.6 0C19.8402 0 20.9603 0 21.816 0.435974Q22.3804 0.723593 22.8284 1.17157Q23.2764 1.61955 23.564 2.18404C24 3.03969 24 4.15979 24 6.4L24 13.6C24 15.8402 24 16.9603 23.564 17.816Q23.2764 18.3804 22.8284 18.8284Q22.3804 19.2764 21.816 19.564C20.9603 20 19.8402 20 17.6 20L6.4 20C4.15979 20 3.03969 20 2.18404 19.564Q1.61955 19.2764 1.17157 18.8284Q0.723594 18.3804 0.435974 17.816C0 16.9603 0 15.8402 0 13.6L0 6.4C0 4.15979 0 3.03969 0.435974 2.18404Q0.723594 1.61955 1.17157 1.17157Q1.61955 0.723594 2.18404 0.435974C3.03969 0 4.15979 0 6.4 0L12 0Z"
|
||||||
fill="#FFFFFF" fill-opacity="0.051" />
|
fill="#FFFFFF" fill-opacity="0.051" />
|
||||||
<path
|
<path
|
||||||
d="M4.9427 0.0799475L0.154716 5.27144Q0.144837 5.28216 0.138427 5.29524Q0.132016 5.30833 0.129608 5.3227Q0.127199 5.33707 0.128993 5.35153Q0.130787 5.36599 0.136635 5.37934Q0.142482 5.39269 0.151896 5.40381Q0.16131 5.41493 0.173507 5.42291Q0.185705 5.43088 0.19967 5.43504Q0.213636 5.4392 0.228208 5.4392L3.06459 5.4392Q3.08448 5.4392 3.10285 5.44681Q3.12123 5.45442 3.13529 5.46848Q3.14935 5.48254 3.15696 5.50092Q3.16457 5.51929 3.16457 5.53917L3.16457 9.90003Q3.16457 9.91991 3.17218 9.93828Q3.17979 9.95666 3.19385 9.97072Q3.20791 9.98478 3.22629 9.99239Q3.24466 10 3.26455 10L6.74521 10Q6.7651 10 6.78347 9.99239Q6.80184 9.98478 6.8159 9.97072Q6.82997 9.95666 6.83758 9.93828Q6.84519 9.91991 6.84519 9.90003L6.84519 5.53917Q6.84519 5.51929 6.8528 5.50091Q6.86041 5.48254 6.87447 5.46848Q6.88853 5.45442 6.9069 5.44681Q6.92527 5.4392 6.94516 5.4392L9.77281 5.4392Q9.78736 5.4392 9.8013 5.43505Q9.81525 5.4309 9.82744 5.42295Q9.83962 5.415 9.84904 5.4039Q9.85845 5.39281 9.86431 5.37949Q9.87017 5.36617 9.87199 5.35173Q9.87382 5.3373 9.87145 5.32294Q9.86908 5.30858 9.86271 5.2955Q9.85635 5.28241 9.84652 5.27168L5.0899 0.0801888Q5.07573 0.064719 5.05653 0.0562526Q5.03734 0.0477862 5.01635 0.0477518Q4.99537 0.0477174 4.97615 0.0561208Q4.95692 0.0645242 4.9427 0.0799475Z"
|
d="M4.9427 0.0799475L0.154716 5.27144Q0.144837 5.28216 0.138427 5.29524Q0.132016 5.30833 0.129608 5.3227Q0.127199 5.33707 0.128993 5.35153Q0.130787 5.36599 0.136635 5.37934Q0.142482 5.39269 0.151896 5.40381Q0.16131 5.41493 0.173507 5.42291Q0.185705 5.43088 0.19967 5.43504Q0.213636 5.4392 0.228208 5.4392L3.06459 5.4392Q3.08448 5.4392 3.10285 5.44681Q3.12123 5.45442 3.13529 5.46848Q3.14935 5.48254 3.15696 5.50092Q3.16457 5.51929 3.16457 5.53917L3.16457 9.90003Q3.16457 9.91991 3.17218 9.93828Q3.17979 9.95666 3.19385 9.97072Q3.20791 9.98478 3.22629 9.99239Q3.24466 10 3.26455 10L6.74521 10Q6.7651 10 6.78347 9.99239Q6.80184 9.98478 6.8159 9.97072Q6.82997 9.95666 6.83758 9.93828Q6.84519 9.91991 6.84519 9.90003L6.84519 5.53917Q6.84519 5.51929 6.8528 5.50091Q6.86041 5.48254 6.87447 5.46848Q6.88853 5.45442 6.9069 5.44681Q6.92527 5.4392 6.94516 5.4392L9.77281 5.4392Q9.78736 5.4392 9.8013 5.43505Q9.81525 5.4309 9.82744 5.42295Q9.83962 5.415 9.84904 5.4039Q9.85845 5.39281 9.86431 5.37949Q9.87017 5.36617 9.87199 5.35173Q9.87382 5.3373 9.87145 5.32294Q9.86908 5.30858 9.86271 5.2955Q9.85635 5.28241 9.84652 5.27168L5.0899 0.0801888Q5.07573 0.064719 5.05653 0.0562526Q5.03734 0.0477862 5.01635 0.0477518Q4.99537 0.0477174 4.97615 0.0561208Q4.95692 0.0645242 4.9427 0.0799475Z"
|
||||||
fill="none" stroke-width="1.3" stroke="#E5DFD5" transform="translate(7 5)" />
|
fill="none" stroke-width="1.3" stroke="#E5DFD5" transform="translate(7 5)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
|
@ -1,23 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="noise"></div>
|
<div class="noise"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.noise {
|
.noise {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
width: calc(100vw - 2px);
|
width: calc(100vw - 2px);
|
||||||
height: calc(100vh - 2px);
|
height: calc(100vh - 2px);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background-image: url("/noise.webp");
|
background-image: url("/noise.webp");
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
mix-blend-mode: multiply;
|
mix-blend-mode: multiply;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,122 +1,122 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="['result', { selected }]"
|
:class="['result', { selected }]"
|
||||||
@click="$emit('select')"
|
@click="$emit('select')"
|
||||||
:ref="el => { if (selected && el) $emit('setRef', el as HTMLElement) }">
|
:ref="el => { if (selected && el) $emit('setRef', el as HTMLElement) }">
|
||||||
<template v-if="item.content_type === 'image'">
|
<template v-if="item.content_type === 'image'">
|
||||||
<img
|
<img
|
||||||
v-if="imageUrl"
|
v-if="imageUrl"
|
||||||
:src="imageUrl"
|
:src="imageUrl"
|
||||||
alt="Image"
|
alt="Image"
|
||||||
class="image"
|
class="image"
|
||||||
@error="$emit('imageError')" />
|
@error="$emit('imageError')" />
|
||||||
<IconsImage v-else class="icon" />
|
<IconsImage v-else class="icon" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="hasFavicon(item.favicon ?? '')">
|
<template v-else-if="hasFavicon(item.favicon ?? '')">
|
||||||
<img
|
<img
|
||||||
v-if="item.favicon"
|
v-if="item.favicon"
|
||||||
:src="getFaviconFromDb(item.favicon)"
|
:src="getFaviconFromDb(item.favicon)"
|
||||||
alt="Favicon"
|
alt="Favicon"
|
||||||
class="favicon"
|
class="favicon"
|
||||||
@error="
|
@error="
|
||||||
($event.target as HTMLImageElement).src = '/public/icons/Link.svg'
|
($event.target as HTMLImageElement).src = '/public/icons/Link.svg'
|
||||||
" />
|
" />
|
||||||
<IconsLink v-else class="icon" />
|
<IconsLink v-else class="icon" />
|
||||||
</template>
|
</template>
|
||||||
<IconsFile
|
<IconsFile
|
||||||
class="icon"
|
class="icon"
|
||||||
v-else-if="item.content_type === ContentType.File" />
|
v-else-if="item.content_type === ContentType.File" />
|
||||||
<IconsText
|
<IconsText
|
||||||
class="icon"
|
class="icon"
|
||||||
v-else-if="item.content_type === ContentType.Text" />
|
v-else-if="item.content_type === ContentType.Text" />
|
||||||
<svg
|
<svg
|
||||||
v-else-if="item.content_type === ContentType.Color"
|
v-else-if="item.content_type === ContentType.Color"
|
||||||
width="18"
|
width="18"
|
||||||
height="18"
|
height="18"
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<rect width="18" height="18" />
|
<rect width="18" height="18" />
|
||||||
<path
|
<path
|
||||||
d="M9 18C12.2154 18 15.1865 16.2846 16.7942 13.5C18.4019 10.7154 18.4019 7.28461 16.7942 4.5C15.1865 1.71539 12.2154 -1.22615e-06 9 0C5.78461 0 2.81347 1.71539 1.20577 4.5C-0.401925 7.28461 -0.401923 10.7154 1.20577 13.5C2.81347 16.2846 5.78461 18 9 18Z"
|
d="M9 18C12.2154 18 15.1865 16.2846 16.7942 13.5C18.4019 10.7154 18.4019 7.28461 16.7942 4.5C15.1865 1.71539 12.2154 -1.22615e-06 9 0C5.78461 0 2.81347 1.71539 1.20577 4.5C-0.401925 7.28461 -0.401923 10.7154 1.20577 13.5C2.81347 16.2846 5.78461 18 9 18Z"
|
||||||
fill="#E5DFD5" />
|
fill="#E5DFD5" />
|
||||||
<path
|
<path
|
||||||
d="M9 16C7.14348 16 5.36301 15.2625 4.05025 13.9497C2.7375 12.637 2 10.8565 2 9C2 7.14348 2.7375 5.36301 4.05025 4.05025C5.36301 2.7375 7.14348 2 9 2C10.8565 2 12.637 2.7375 13.9497 4.05025C15.2625 5.36301 16 7.14348 16 9C16 10.8565 15.2625 12.637 13.9497 13.9497C12.637 15.2625 10.8565 16 9 16Z"
|
d="M9 16C7.14348 16 5.36301 15.2625 4.05025 13.9497C2.7375 12.637 2 10.8565 2 9C2 7.14348 2.7375 5.36301 4.05025 4.05025C5.36301 2.7375 7.14348 2 9 2C10.8565 2 12.637 2.7375 13.9497 4.05025C15.2625 5.36301 16 7.14348 16 9C16 10.8565 15.2625 12.637 13.9497 13.9497C12.637 15.2625 10.8565 16 9 16Z"
|
||||||
:fill="item.content" />
|
:fill="item.content" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<IconsCode
|
<IconsCode
|
||||||
class="icon"
|
class="icon"
|
||||||
v-else-if="item.content_type === ContentType.Code" />
|
v-else-if="item.content_type === ContentType.Code" />
|
||||||
<span v-if="item.content_type === ContentType.Image">
|
<span v-if="item.content_type === ContentType.Image">
|
||||||
Image ({{ dimensions || "Loading..." }})
|
Image ({{ dimensions || "Loading..." }})
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ truncateContent(item.content) }}</span>
|
<span v-else>{{ truncateContent(item.content) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ContentType } from "~/types/types";
|
import { ContentType } from "~/types/types";
|
||||||
import type { HistoryItem } from "~/types/types";
|
import type { HistoryItem } from "~/types/types";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
item: HistoryItem;
|
item: HistoryItem;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
dimensions?: string;
|
dimensions?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: "select"): void;
|
(e: "select"): void;
|
||||||
(e: "imageError"): void;
|
(e: "imageError"): void;
|
||||||
(e: "setRef", el: HTMLElement): void;
|
(e: "setRef", el: HTMLElement): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const hasFavicon = (str: string): boolean => {
|
const hasFavicon = (str: string): boolean => {
|
||||||
return str.trim() !== "";
|
return str.trim() !== "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFaviconFromDb = (favicon: string): string => {
|
const getFaviconFromDb = (favicon: string): string => {
|
||||||
return `data:image/png;base64,${favicon}`;
|
return `data:image/png;base64,${favicon}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const truncateContent = (content: string): string => {
|
const truncateContent = (content: string): string => {
|
||||||
const maxWidth = 284;
|
const maxWidth = 284;
|
||||||
const charWidth = 9;
|
const charWidth = 9;
|
||||||
const maxChars = Math.floor(maxWidth / charWidth);
|
const maxChars = Math.floor(maxWidth / charWidth);
|
||||||
return content.length > maxChars
|
return content.length > maxChars
|
||||||
? content.slice(0, maxChars - 3) + "..."
|
? content.slice(0, maxChars - 3) + "..."
|
||||||
: content;
|
: content;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.result {
|
.result {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 11px;
|
padding: 11px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: var(--border);
|
background-color: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.favicon,
|
.favicon,
|
||||||
.image,
|
.image,
|
||||||
.icon {
|
.icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,57 +1,57 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<input
|
<input
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
@input="onInputChange"
|
@input="onInputChange"
|
||||||
class="search"
|
class="search"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="off"
|
autocapitalize="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type to filter entries..." />
|
placeholder="Type to filter entries..." />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
const searchInput = ref<HTMLInputElement | null>(null);
|
const searchInput = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "search", query: string): void;
|
(e: "search", query: string): void;
|
||||||
(e: "searchStarted"): void;
|
(e: "searchStarted"): void;
|
||||||
(e: "focus"): void;
|
(e: "focus"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const onInputChange = () => {
|
const onInputChange = () => {
|
||||||
emit("searchStarted");
|
emit("searchStarted");
|
||||||
emit("search", searchQuery.value);
|
emit("search", searchQuery.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ searchInput });
|
defineExpose({ searchInput });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.topbar {
|
.topbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-inline: 16px;
|
padding-inline: 16px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,222 +1,222 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { HistoryItem } from "../types/types";
|
import { HistoryItem } from "../types/types";
|
||||||
|
|
||||||
const { $history } = useNuxtApp();
|
const { $history } = useNuxtApp();
|
||||||
const { hideApp } = useAppControl();
|
const { hideApp } = useAppControl();
|
||||||
|
|
||||||
export function useActions() {
|
export function useActions() {
|
||||||
const isProcessing = ref(false);
|
const isProcessing = ref(false);
|
||||||
|
|
||||||
const handleAction = async (action: string, item?: HistoryItem) => {
|
const handleAction = async (action: string, item?: HistoryItem) => {
|
||||||
if (!item && action !== "settings" && action !== "delete-all") return;
|
if (!item && action !== "settings" && action !== "delete-all") return;
|
||||||
|
|
||||||
isProcessing.value = true;
|
isProcessing.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "paste-to-app":
|
case "paste-to-app":
|
||||||
await pasteToCurrentApp(item);
|
await pasteToCurrentApp(item);
|
||||||
break;
|
break;
|
||||||
case "copy":
|
case "copy":
|
||||||
// await copyToClipboard(item);
|
// await copyToClipboard(item);
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
await deleteEntry(item);
|
await deleteEntry(item);
|
||||||
break;
|
break;
|
||||||
case "delete-all":
|
case "delete-all":
|
||||||
// await deleteAllEntries();
|
// await deleteAllEntries();
|
||||||
break;
|
break;
|
||||||
case "settings":
|
case "settings":
|
||||||
openSettings();
|
openSettings();
|
||||||
break;
|
break;
|
||||||
case "paste-plain":
|
case "paste-plain":
|
||||||
// await pasteAsPlainText(item);
|
// await pasteAsPlainText(item);
|
||||||
break;
|
break;
|
||||||
case "edit-text":
|
case "edit-text":
|
||||||
// openTextEditor(item);
|
// openTextEditor(item);
|
||||||
break;
|
break;
|
||||||
case "rotate-image":
|
case "rotate-image":
|
||||||
// await rotateImage(item);
|
// await rotateImage(item);
|
||||||
break;
|
break;
|
||||||
case "resize-image":
|
case "resize-image":
|
||||||
// openImageResizer(item);
|
// openImageResizer(item);
|
||||||
break;
|
break;
|
||||||
case "compress-image":
|
case "compress-image":
|
||||||
// await compressImage(item);
|
// await compressImage(item);
|
||||||
break;
|
break;
|
||||||
case "open-file":
|
case "open-file":
|
||||||
// await openFile(item);
|
// await openFile(item);
|
||||||
break;
|
break;
|
||||||
case "compress-file":
|
case "compress-file":
|
||||||
// await compressFile(item);
|
// await compressFile(item);
|
||||||
break;
|
break;
|
||||||
case "open-link":
|
case "open-link":
|
||||||
// await openInBrowser(item);
|
// await openInBrowser(item);
|
||||||
break;
|
break;
|
||||||
case "copy-hex":
|
case "copy-hex":
|
||||||
// await copyColorFormat(item, "hex");
|
// await copyColorFormat(item, "hex");
|
||||||
break;
|
break;
|
||||||
case "copy-rgba":
|
case "copy-rgba":
|
||||||
// await copyColorFormat(item, "rgba");
|
// await copyColorFormat(item, "rgba");
|
||||||
break;
|
break;
|
||||||
case "copy-hsla":
|
case "copy-hsla":
|
||||||
// await copyColorFormat(item, "hsla");
|
// await copyColorFormat(item, "hsla");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`Action ${action} not implemented`);
|
console.warn(`Action ${action} not implemented`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error executing action ${action}:`, error);
|
console.error(`Error executing action ${action}:`, error);
|
||||||
} finally {
|
} finally {
|
||||||
isProcessing.value = false;
|
isProcessing.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pasteToCurrentApp = async (item?: HistoryItem) => {
|
const pasteToCurrentApp = async (item?: HistoryItem) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
let content = item.content;
|
let content = item.content;
|
||||||
let contentType: string = item.content_type;
|
let contentType: string = item.content_type;
|
||||||
if (contentType === "image") {
|
if (contentType === "image") {
|
||||||
try {
|
try {
|
||||||
content = await $history.readImage({ filename: content });
|
content = await $history.readImage({ filename: content });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error reading image file:", error);
|
console.error("Error reading image file:", error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await hideApp();
|
await hideApp();
|
||||||
await $history.writeAndPaste({ content, contentType });
|
await $history.writeAndPaste({ content, contentType });
|
||||||
};
|
};
|
||||||
|
|
||||||
// const copyToClipboard = async (item?: HistoryItem) => {
|
// const copyToClipboard = async (item?: HistoryItem) => {
|
||||||
// if (!item) return;
|
// if (!item) return;
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// switch (item.content_type) {
|
// switch (item.content_type) {
|
||||||
// case ContentType.Text:
|
// case ContentType.Text:
|
||||||
// case ContentType.Link:
|
// case ContentType.Link:
|
||||||
// case ContentType.Code:
|
// case ContentType.Code:
|
||||||
// await writeText(item.content);
|
// await writeText(item.content);
|
||||||
// break;
|
// break;
|
||||||
// case ContentType.Image:
|
// case ContentType.Image:
|
||||||
// await invoke("copy_image_to_clipboard", { path: item.file_path });
|
// await invoke("copy_image_to_clipboard", { path: item.file_path });
|
||||||
// break;
|
// break;
|
||||||
// case ContentType.File:
|
// case ContentType.File:
|
||||||
// await invoke("copy_file_reference", { path: item.file_path });
|
// await invoke("copy_file_reference", { path: item.file_path });
|
||||||
// break;
|
// break;
|
||||||
// case ContentType.Color:
|
// case ContentType.Color:
|
||||||
// await writeText(item.content);
|
// await writeText(item.content);
|
||||||
// break;
|
// break;
|
||||||
// default:
|
// default:
|
||||||
// console.warn(`Copying type ${item.content_type} not implemented`);
|
// console.warn(`Copying type ${item.content_type} not implemented`);
|
||||||
// }
|
// }
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error("Failed to copy to clipboard:", error);
|
// console.error("Failed to copy to clipboard:", error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const deleteEntry = async (item?: HistoryItem) => {
|
const deleteEntry = async (item?: HistoryItem) => {
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
try {
|
try {
|
||||||
await invoke("delete_history_item", { id: item.id });
|
await invoke("delete_history_item", { id: item.id });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete entry:", error);
|
console.error("Failed to delete entry:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const deleteAllEntries = async () => {
|
// const deleteAllEntries = async () => {
|
||||||
// try {
|
// try {
|
||||||
// await invoke('delete_all_history');
|
// await invoke('delete_all_history');
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to delete all entries:', error);
|
// console.error('Failed to delete all entries:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const openSettings = () => {
|
const openSettings = () => {
|
||||||
navigateTo("/settings");
|
navigateTo("/settings");
|
||||||
};
|
};
|
||||||
|
|
||||||
// const pasteAsPlainText = async (item?: HistoryItem) => {
|
// const pasteAsPlainText = async (item?: HistoryItem) => {
|
||||||
// if (!item) return;
|
// if (!item) return;
|
||||||
// try {
|
// try {
|
||||||
// await invoke('paste_as_plain_text', { content: item.content });
|
// await invoke('paste_as_plain_text', { content: item.content });
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to paste as plain text:', error);
|
// console.error('Failed to paste as plain text:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const openTextEditor = (item?: HistoryItem) => {
|
// const openTextEditor = (item?: HistoryItem) => {
|
||||||
// if (!item) return;
|
// if (!item) return;
|
||||||
// // Implement logic to open text editor with the content
|
// // Implement logic to open text editor with the content
|
||||||
// // This might use Nuxt router or a modal based on your app architecture
|
// // This might use Nuxt router or a modal based on your app architecture
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const rotateImage = async (item?: HistoryItem) => {
|
// const rotateImage = async (item?: HistoryItem) => {
|
||||||
// if (!item || item.content_type !== ContentType.Image) return;
|
// if (!item || item.content_type !== ContentType.Image) return;
|
||||||
// try {
|
// try {
|
||||||
// await invoke('rotate_image', { path: item.file_path });
|
// await invoke('rotate_image', { path: item.file_path });
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to rotate image:', error);
|
// console.error('Failed to rotate image:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const openImageResizer = (item?: HistoryItem) => {
|
// const openImageResizer = (item?: HistoryItem) => {
|
||||||
// if (!item || item.content_type !== ContentType.Image) return;
|
// if (!item || item.content_type !== ContentType.Image) return;
|
||||||
// // Implement logic to open image resizer UI for this image
|
// // Implement logic to open image resizer UI for this image
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const compressImage = async (item?: HistoryItem) => {
|
// const compressImage = async (item?: HistoryItem) => {
|
||||||
// if (!item || item.content_type !== ContentType.Image) return;
|
// if (!item || item.content_type !== ContentType.Image) return;
|
||||||
// try {
|
// try {
|
||||||
// await invoke('compress_image', { path: item.file_path });
|
// await invoke('compress_image', { path: item.file_path });
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to compress image:', error);
|
// console.error('Failed to compress image:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const openFile = async (item?: HistoryItem) => {
|
// const openFile = async (item?: HistoryItem) => {
|
||||||
// if (!item || item.content_type !== ContentType.File) return;
|
// if (!item || item.content_type !== ContentType.File) return;
|
||||||
// try {
|
// try {
|
||||||
// await invoke('open_file', { path: item.file_path });
|
// await invoke('open_file', { path: item.file_path });
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to open file:', error);
|
// console.error('Failed to open file:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const compressFile = async (item?: HistoryItem) => {
|
// const compressFile = async (item?: HistoryItem) => {
|
||||||
// if (!item || item.content_type !== ContentType.File) return;
|
// if (!item || item.content_type !== ContentType.File) return;
|
||||||
// try {
|
// try {
|
||||||
// await invoke('compress_file', { path: item.file_path });
|
// await invoke('compress_file', { path: item.file_path });
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to compress file:', error);
|
// console.error('Failed to compress file:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const openInBrowser = async (item?: HistoryItem) => {
|
// const openInBrowser = async (item?: HistoryItem) => {
|
||||||
// if (!item || item.content_type !== ContentType.Link) return;
|
// if (!item || item.content_type !== ContentType.Link) return;
|
||||||
// try {
|
// try {
|
||||||
// await invoke('open_url', { url: item.content });
|
// await invoke('open_url', { url: item.content });
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Failed to open URL in browser:', error);
|
// console.error('Failed to open URL in browser:', error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// const copyColorFormat = async (item?: HistoryItem, format: 'hex' | 'rgba' | 'hsla' = 'hex') => {
|
// const copyColorFormat = async (item?: HistoryItem, format: 'hex' | 'rgba' | 'hsla' = 'hex') => {
|
||||||
// if (!item || item.content_type !== ContentType.Color) return;
|
// if (!item || item.content_type !== ContentType.Color) return;
|
||||||
// try {
|
// try {
|
||||||
// const formattedColor = await invoke('get_color_format', {
|
// const formattedColor = await invoke('get_color_format', {
|
||||||
// color: item.content,
|
// color: item.content,
|
||||||
// format
|
// format
|
||||||
// });
|
// });
|
||||||
// await writeText(formattedColor as string);
|
// await writeText(formattedColor as string);
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error(`Failed to copy color as ${format}:`, error);
|
// console.error(`Failed to copy color as ${format}:`, error);
|
||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleAction,
|
handleAction,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { app, window } from "@tauri-apps/api";
|
import { app, window } from "@tauri-apps/api";
|
||||||
|
|
||||||
export function useAppControl() {
|
export function useAppControl() {
|
||||||
const hideApp = async (): Promise<void> => {
|
const hideApp = async (): Promise<void> => {
|
||||||
await app.hide();
|
await app.hide();
|
||||||
await window.getCurrentWindow().hide();
|
await window.getCurrentWindow().hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hideApp
|
hideApp
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
devtools: { enabled: false },
|
devtools: { enabled: false },
|
||||||
compatibilityDate: "2024-07-04",
|
compatibilityDate: "2024-07-04",
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
charset: "utf-8",
|
charset: "utf-8",
|
||||||
viewport:
|
viewport:
|
||||||
"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0",
|
"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
scss: {
|
scss: {
|
||||||
api: "modern-compiler",
|
api: "modern-compiler",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
56
package.json
56
package.json
|
@ -1,28 +1,28 @@
|
||||||
{
|
{
|
||||||
"name": "nuxt-app",
|
"name": "nuxt-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tauri build",
|
"build": "tauri build",
|
||||||
"dev": "tauri dev",
|
"dev": "tauri dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.3.0",
|
"@tauri-apps/api": "2.3.0",
|
||||||
"@tauri-apps/cli": "2.3.1",
|
"@tauri-apps/cli": "2.3.1",
|
||||||
"@tauri-apps/plugin-autostart": "2.2.0",
|
"@tauri-apps/plugin-autostart": "2.2.0",
|
||||||
"@tauri-apps/plugin-os": "2.2.1",
|
"@tauri-apps/plugin-os": "2.2.1",
|
||||||
"nuxt": "3.16.0",
|
"nuxt": "3.16.0",
|
||||||
"overlayscrollbars": "2.11.1",
|
"overlayscrollbars": "2.11.1",
|
||||||
"overlayscrollbars-vue": "0.5.9",
|
"overlayscrollbars-vue": "0.5.9",
|
||||||
"sass-embedded": "1.85.1",
|
"sass-embedded": "1.85.1",
|
||||||
"uuid": "11.1.0",
|
"uuid": "11.1.0",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"@waradu/keyboard": "4.2.0"
|
"@waradu/keyboard": "4.2.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"chokidar": "^3.6.0"
|
"chokidar": "^3.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1892
pages/index.vue
1892
pages/index.vue
File diff suppressed because it is too large
Load diff
|
@ -1,209 +1,209 @@
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<NuxtLink to="/" class="back">
|
<NuxtLink to="/" class="back">
|
||||||
<img src="../public/back_arrow.svg" />
|
<img src="../public/back_arrow.svg" />
|
||||||
<p>Back</p>
|
<p>Back</p>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
<div class="names">
|
<div class="names">
|
||||||
<p style="line-height: 14px">Startup</p>
|
<p style="line-height: 14px">Startup</p>
|
||||||
<p style="line-height: 34px">Qopy Hotkey</p>
|
<p style="line-height: 34px">Qopy Hotkey</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<div class="launch">
|
<div class="launch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="launch"
|
id="launch"
|
||||||
v-model="autostart"
|
v-model="autostart"
|
||||||
@change="toggleAutostart" />
|
@change="toggleAutostart" />
|
||||||
<label for="launch" class="checkmark">
|
<label for="launch" class="checkmark">
|
||||||
<svg
|
<svg
|
||||||
width="14"
|
width="14"
|
||||||
height="14"
|
height="14"
|
||||||
viewBox="0 0 14 14"
|
viewBox="0 0 14 14"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<rect width="14" height="14" />
|
<rect width="14" height="14" />
|
||||||
<path
|
<path
|
||||||
id="Path"
|
id="Path"
|
||||||
d="M0 2.00696L2.25015 4.25L6 0"
|
d="M0 2.00696L2.25015 4.25L6 0"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="#E5DFD5"
|
stroke="#E5DFD5"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
transform="translate(4 5)" />
|
transform="translate(4 5)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<p for="launch">Launch Qopy at login</p>
|
<p for="launch">Launch Qopy at login</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
class="keybind-input"
|
class="keybind-input"
|
||||||
ref="keybindInput"
|
ref="keybindInput"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
:class="{ 'empty-keybind': showEmptyKeybindError }">
|
:class="{ 'empty-keybind': showEmptyKeybindError }">
|
||||||
<span class="key" v-if="keybind.length === 0">Click here</span>
|
<span class="key" v-if="keybind.length === 0">Click here</span>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span
|
<span
|
||||||
:key="index"
|
:key="index"
|
||||||
class="key"
|
class="key"
|
||||||
:class="{ modifier: isModifier(key) }"
|
:class="{ modifier: isModifier(key) }"
|
||||||
v-for="(key, index) in keybind">
|
v-for="(key, index) in keybind">
|
||||||
{{ keyToLabel(key) }}
|
{{ keyToLabel(key) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BottomBar
|
<BottomBar
|
||||||
:primary-action="{
|
:primary-action="{
|
||||||
text: 'Save',
|
text: 'Save',
|
||||||
icon: IconsEnter,
|
icon: IconsEnter,
|
||||||
onClick: saveKeybind,
|
onClick: saveKeybind,
|
||||||
showModifier: true,
|
showModifier: true,
|
||||||
}" />
|
}" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||||
import { platform } from "@tauri-apps/plugin-os";
|
import { platform } from "@tauri-apps/plugin-os";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { KeyValues, KeyLabels } from "../types/keys";
|
import { KeyValues, KeyLabels } from "../types/keys";
|
||||||
import { disable, enable } from "@tauri-apps/plugin-autostart";
|
import { disable, enable } from "@tauri-apps/plugin-autostart";
|
||||||
import { useNuxtApp } from "#app";
|
import { useNuxtApp } from "#app";
|
||||||
import BottomBar from "../components/BottomBar.vue";
|
import BottomBar from "../components/BottomBar.vue";
|
||||||
import IconsEnter from "~/components/Keys/Enter.vue";
|
import IconsEnter from "~/components/Keys/Enter.vue";
|
||||||
|
|
||||||
const activeModifiers = reactive<Set<KeyValues>>(new Set());
|
const activeModifiers = reactive<Set<KeyValues>>(new Set());
|
||||||
const isKeybindInputFocused = ref(false);
|
const isKeybindInputFocused = ref(false);
|
||||||
const keybind = ref<KeyValues[]>([]);
|
const keybind = ref<KeyValues[]>([]);
|
||||||
const keybindInput = ref<HTMLElement | null>(null);
|
const keybindInput = ref<HTMLElement | null>(null);
|
||||||
const blurredByEscape = ref(false);
|
const blurredByEscape = ref(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const showEmptyKeybindError = ref(false);
|
const showEmptyKeybindError = ref(false);
|
||||||
const autostart = ref(false);
|
const autostart = ref(false);
|
||||||
const { $settings, $keyboard } = useNuxtApp();
|
const { $settings, $keyboard } = useNuxtApp();
|
||||||
|
|
||||||
const listeners: Array<() => void> = [];
|
const listeners: Array<() => void> = [];
|
||||||
|
|
||||||
const modifierKeySet = new Set([
|
const modifierKeySet = new Set([
|
||||||
KeyValues.AltLeft,
|
KeyValues.AltLeft,
|
||||||
KeyValues.AltRight,
|
KeyValues.AltRight,
|
||||||
KeyValues.ControlLeft,
|
KeyValues.ControlLeft,
|
||||||
KeyValues.ControlRight,
|
KeyValues.ControlRight,
|
||||||
KeyValues.MetaLeft,
|
KeyValues.MetaLeft,
|
||||||
KeyValues.MetaRight,
|
KeyValues.MetaRight,
|
||||||
KeyValues.ShiftLeft,
|
KeyValues.ShiftLeft,
|
||||||
KeyValues.ShiftRight,
|
KeyValues.ShiftRight,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isModifier = (key: KeyValues): boolean => {
|
const isModifier = (key: KeyValues): boolean => {
|
||||||
return modifierKeySet.has(key);
|
return modifierKeySet.has(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyToLabel = (key: KeyValues): string => {
|
const keyToLabel = (key: KeyValues): string => {
|
||||||
return KeyLabels[key] || key;
|
return KeyLabels[key] || key;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateKeybindDisplay = () => {
|
const updateKeybindDisplay = () => {
|
||||||
const modifiers = Array.from(activeModifiers);
|
const modifiers = Array.from(activeModifiers);
|
||||||
const nonModifiers = keybind.value.filter((key) => !isModifier(key));
|
const nonModifiers = keybind.value.filter((key) => !isModifier(key));
|
||||||
const sortedModifiers = modifiers.sort();
|
const sortedModifiers = modifiers.sort();
|
||||||
keybind.value = [...sortedModifiers, ...nonModifiers];
|
keybind.value = [...sortedModifiers, ...nonModifiers];
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
isKeybindInputFocused.value = false;
|
isKeybindInputFocused.value = false;
|
||||||
showEmptyKeybindError.value = false;
|
showEmptyKeybindError.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFocus = () => {
|
const onFocus = () => {
|
||||||
isKeybindInputFocused.value = true;
|
isKeybindInputFocused.value = true;
|
||||||
blurredByEscape.value = false;
|
blurredByEscape.value = false;
|
||||||
activeModifiers.clear();
|
activeModifiers.clear();
|
||||||
keybind.value = [];
|
keybind.value = [];
|
||||||
showEmptyKeybindError.value = false;
|
showEmptyKeybindError.value = false;
|
||||||
|
|
||||||
const unlistenAll = $keyboard.listen([$keyboard.Key.All], (event: KeyboardEvent) => {
|
const unlistenAll = $keyboard.listen([$keyboard.Key.All], (event: KeyboardEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const key = event.code as KeyValues;
|
const key = event.code as KeyValues;
|
||||||
|
|
||||||
if (key === KeyValues.Escape) {
|
if (key === KeyValues.Escape) {
|
||||||
blurredByEscape.value = true;
|
blurredByEscape.value = true;
|
||||||
keybindInput.value?.blur();
|
keybindInput.value?.blur();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isModifier(key)) {
|
if (isModifier(key)) {
|
||||||
activeModifiers.add(key);
|
activeModifiers.add(key);
|
||||||
} else {
|
} else {
|
||||||
const nonModifierKey = keybind.value.find(k => !isModifier(k));
|
const nonModifierKey = keybind.value.find(k => !isModifier(k));
|
||||||
if (!nonModifierKey || nonModifierKey === key) {
|
if (!nonModifierKey || nonModifierKey === key) {
|
||||||
keybind.value = Array.from(activeModifiers);
|
keybind.value = Array.from(activeModifiers);
|
||||||
if (nonModifierKey !== key) keybind.value.push(key);
|
if (nonModifierKey !== key) keybind.value.push(key);
|
||||||
} else {
|
} else {
|
||||||
keybind.value = [ ...Array.from(activeModifiers), key];
|
keybind.value = [ ...Array.from(activeModifiers), key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateKeybindDisplay();
|
updateKeybindDisplay();
|
||||||
showEmptyKeybindError.value = false;
|
showEmptyKeybindError.value = false;
|
||||||
}, { prevent: true });
|
}, { prevent: true });
|
||||||
listeners.push(unlistenAll);
|
listeners.push(unlistenAll);
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveKeybind = async () => {
|
const saveKeybind = async () => {
|
||||||
const finalKeybind = keybind.value.filter(k => k);
|
const finalKeybind = keybind.value.filter(k => k);
|
||||||
if (finalKeybind.length > 0) {
|
if (finalKeybind.length > 0) {
|
||||||
await $settings.saveSetting("keybind", JSON.stringify(finalKeybind));
|
await $settings.saveSetting("keybind", JSON.stringify(finalKeybind));
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} else {
|
} else {
|
||||||
showEmptyKeybindError.value = true;
|
showEmptyKeybindError.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAutostart = async () => {
|
const toggleAutostart = async () => {
|
||||||
if (autostart.value === true) {
|
if (autostart.value === true) {
|
||||||
await enable();
|
await enable();
|
||||||
} else {
|
} else {
|
||||||
await disable();
|
await disable();
|
||||||
}
|
}
|
||||||
await $settings.saveSetting("autostart", autostart.value ? "true" : "false");
|
await $settings.saveSetting("autostart", autostart.value ? "true" : "false");
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
autostart.value = (await $settings.getSetting("autostart")) === "true";
|
autostart.value = (await $settings.getSetting("autostart")) === "true";
|
||||||
|
|
||||||
const metaOrCtrlKey = $keyboard.currentOS === "macos" ? $keyboard.Key.Meta : $keyboard.Key.Control;
|
const metaOrCtrlKey = $keyboard.currentOS === "macos" ? $keyboard.Key.Meta : $keyboard.Key.Control;
|
||||||
listeners.push(
|
listeners.push(
|
||||||
$keyboard.listen([metaOrCtrlKey, $keyboard.Key.Enter], saveKeybind, { prevent: true, ignoreIfEditable: true })
|
$keyboard.listen([metaOrCtrlKey, $keyboard.Key.Enter], saveKeybind, { prevent: true, ignoreIfEditable: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
listeners.push(
|
listeners.push(
|
||||||
$keyboard.listen([$keyboard.Key.Escape], () => {
|
$keyboard.listen([$keyboard.Key.Escape], () => {
|
||||||
if (!isKeybindInputFocused.value && !blurredByEscape.value) {
|
if (!isKeybindInputFocused.value && !blurredByEscape.value) {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
}
|
}
|
||||||
if(blurredByEscape.value) blurredByEscape.value = false;
|
if(blurredByEscape.value) blurredByEscape.value = false;
|
||||||
}, { prevent: true })
|
}, { prevent: true })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
listeners.forEach(unlisten => unlisten());
|
listeners.forEach(unlisten => unlisten());
|
||||||
listeners.length = 0;
|
listeners.length = 0;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "/styles/settings.scss";
|
@use "/styles/settings.scss";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import type { HistoryItem } from "~/types/types";
|
import type { HistoryItem } from "~/types/types";
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
return {
|
return {
|
||||||
provide: {
|
provide: {
|
||||||
history: {
|
history: {
|
||||||
async getHistory(): Promise<HistoryItem[]> {
|
async getHistory(): Promise<HistoryItem[]> {
|
||||||
return await invoke<HistoryItem[]>("get_history");
|
return await invoke<HistoryItem[]>("get_history");
|
||||||
},
|
},
|
||||||
|
|
||||||
async addHistoryItem(item: HistoryItem): Promise<void> {
|
async addHistoryItem(item: HistoryItem): Promise<void> {
|
||||||
await invoke<void>("add_history_item", { item });
|
await invoke<void>("add_history_item", { item });
|
||||||
},
|
},
|
||||||
|
|
||||||
async searchHistory(query: string): Promise<HistoryItem[]> {
|
async searchHistory(query: string): Promise<HistoryItem[]> {
|
||||||
try {
|
try {
|
||||||
return await invoke<HistoryItem[]>("search_history", { query });
|
return await invoke<HistoryItem[]>("search_history", { query });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error searching history:", error);
|
console.error("Error searching history:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadHistoryChunk(
|
async loadHistoryChunk(
|
||||||
offset: number,
|
offset: number,
|
||||||
limit: number
|
limit: number
|
||||||
): Promise<HistoryItem[]> {
|
): Promise<HistoryItem[]> {
|
||||||
try {
|
try {
|
||||||
return await invoke<HistoryItem[]>("load_history_chunk", {
|
return await invoke<HistoryItem[]>("load_history_chunk", {
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading history chunk:", error);
|
console.error("Error loading history chunk:", error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteHistoryItem(id: string): Promise<void> {
|
async deleteHistoryItem(id: string): Promise<void> {
|
||||||
await invoke<void>("delete_history_item", { id });
|
await invoke<void>("delete_history_item", { id });
|
||||||
},
|
},
|
||||||
|
|
||||||
async clearHistory(): Promise<void> {
|
async clearHistory(): Promise<void> {
|
||||||
await invoke<void>("clear_history");
|
await invoke<void>("clear_history");
|
||||||
},
|
},
|
||||||
|
|
||||||
async writeAndPaste(data: {
|
async writeAndPaste(data: {
|
||||||
content: string;
|
content: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
await invoke<void>("write_and_paste", data);
|
await invoke<void>("write_and_paste", data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async readImage(data: { filename: string }): Promise<string> {
|
async readImage(data: { filename: string }): Promise<string> {
|
||||||
return await invoke<string>("read_image", data);
|
return await invoke<string>("read_image", data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import { platform } from "@tauri-apps/plugin-os";
|
import { platform } from "@tauri-apps/plugin-os";
|
||||||
import { useKeyboard, Key } from "@waradu/keyboard";
|
import { useKeyboard, Key } from "@waradu/keyboard";
|
||||||
|
|
||||||
export default defineNuxtPlugin(async (nuxtApp) => {
|
export default defineNuxtPlugin(async (nuxtApp) => {
|
||||||
const keyboardInstance = useKeyboard();
|
const keyboardInstance = useKeyboard();
|
||||||
let currentOS = "windows";
|
let currentOS = "windows";
|
||||||
try {
|
try {
|
||||||
const osName = await Promise.resolve(platform());
|
const osName = await Promise.resolve(platform());
|
||||||
currentOS = osName.toLowerCase().includes("mac") ? "macos" : "windows";
|
currentOS = osName.toLowerCase().includes("mac") ? "macos" : "windows";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error detecting platform:", error);
|
console.error("Error detecting platform:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer initialization until the app is mounted
|
// Defer initialization until the app is mounted
|
||||||
nuxtApp.hook('app:mounted', () => {
|
nuxtApp.hook('app:mounted', () => {
|
||||||
keyboardInstance.init();
|
keyboardInstance.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
nuxtApp.provide('keyboard', {
|
nuxtApp.provide('keyboard', {
|
||||||
listen: keyboardInstance.listen.bind(keyboardInstance),
|
listen: keyboardInstance.listen.bind(keyboardInstance),
|
||||||
init: keyboardInstance.init.bind(keyboardInstance),
|
init: keyboardInstance.init.bind(keyboardInstance),
|
||||||
Key,
|
Key,
|
||||||
currentOS,
|
currentOS,
|
||||||
// Provide a clear method if users need to manually clear all listeners from the instance
|
// Provide a clear method if users need to manually clear all listeners from the instance
|
||||||
clearAll: keyboardInstance.clear ? keyboardInstance.clear.bind(keyboardInstance) : () => { console.warn('@waradu/keyboard instance does not have a clear method'); }
|
clearAll: keyboardInstance.clear ? keyboardInstance.clear.bind(keyboardInstance) : () => { console.warn('@waradu/keyboard instance does not have a clear method'); }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import type { HistoryItem } from '~/types/types';
|
import type { HistoryItem } from '~/types/types';
|
||||||
|
|
||||||
interface GroupedHistory {
|
interface GroupedHistory {
|
||||||
label: string;
|
label: string;
|
||||||
items: HistoryItem[];
|
items: HistoryItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedGroupIndex = ref(0);
|
const selectedGroupIndex = ref(0);
|
||||||
const selectedItemIndex = ref(0);
|
const selectedItemIndex = ref(0);
|
||||||
const selectedElement = ref<HTMLElement | null>(null);
|
const selectedElement = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const useSelectedResult = (groupedHistory: Ref<GroupedHistory[]>) => {
|
const useSelectedResult = (groupedHistory: Ref<GroupedHistory[]>) => {
|
||||||
const selectedItem = computed<HistoryItem | undefined>(() => {
|
const selectedItem = computed<HistoryItem | undefined>(() => {
|
||||||
const group = groupedHistory.value[selectedGroupIndex.value];
|
const group = groupedHistory.value[selectedGroupIndex.value];
|
||||||
return group?.items[selectedItemIndex.value] ?? undefined;
|
return group?.items[selectedItemIndex.value] ?? undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSelected = (groupIndex: number, itemIndex: number): boolean => {
|
const isSelected = (groupIndex: number, itemIndex: number): boolean => {
|
||||||
return selectedGroupIndex.value === groupIndex && selectedItemIndex.value === itemIndex;
|
return selectedGroupIndex.value === groupIndex && selectedItemIndex.value === itemIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectNext = (): void => {
|
const selectNext = (): void => {
|
||||||
const currentGroup = groupedHistory.value[selectedGroupIndex.value];
|
const currentGroup = groupedHistory.value[selectedGroupIndex.value];
|
||||||
if (selectedItemIndex.value < currentGroup.items.length - 1) {
|
if (selectedItemIndex.value < currentGroup.items.length - 1) {
|
||||||
selectedItemIndex.value++;
|
selectedItemIndex.value++;
|
||||||
} else if (selectedGroupIndex.value < groupedHistory.value.length - 1) {
|
} else if (selectedGroupIndex.value < groupedHistory.value.length - 1) {
|
||||||
selectedGroupIndex.value++;
|
selectedGroupIndex.value++;
|
||||||
selectedItemIndex.value = 0;
|
selectedItemIndex.value = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPrevious = (): void => {
|
const selectPrevious = (): void => {
|
||||||
if (selectedItemIndex.value > 0) {
|
if (selectedItemIndex.value > 0) {
|
||||||
selectedItemIndex.value--;
|
selectedItemIndex.value--;
|
||||||
} else if (selectedGroupIndex.value > 0) {
|
} else if (selectedGroupIndex.value > 0) {
|
||||||
selectedGroupIndex.value--;
|
selectedGroupIndex.value--;
|
||||||
selectedItemIndex.value = groupedHistory.value[selectedGroupIndex.value].items.length - 1;
|
selectedItemIndex.value = groupedHistory.value[selectedGroupIndex.value].items.length - 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectItem = (groupIndex: number, itemIndex: number): void => {
|
const selectItem = (groupIndex: number, itemIndex: number): void => {
|
||||||
selectedGroupIndex.value = groupIndex;
|
selectedGroupIndex.value = groupIndex;
|
||||||
selectedItemIndex.value = itemIndex;
|
selectedItemIndex.value = itemIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedItem,
|
selectedItem,
|
||||||
isSelected,
|
isSelected,
|
||||||
selectNext,
|
selectNext,
|
||||||
selectPrevious,
|
selectPrevious,
|
||||||
selectItem,
|
selectItem,
|
||||||
selectedElement
|
selectedElement
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
return {
|
return {
|
||||||
provide: {
|
provide: {
|
||||||
selectedResult: {
|
selectedResult: {
|
||||||
selectedGroupIndex,
|
selectedGroupIndex,
|
||||||
selectedItemIndex,
|
selectedItemIndex,
|
||||||
selectedElement,
|
selectedElement,
|
||||||
useSelectedResult
|
useSelectedResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
|
@ -1,17 +1,17 @@
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
return {
|
return {
|
||||||
provide: {
|
provide: {
|
||||||
settings: {
|
settings: {
|
||||||
async getSetting(key: string): Promise<string> {
|
async getSetting(key: string): Promise<string> {
|
||||||
return await invoke<string>("get_setting", { key });
|
return await invoke<string>("get_setting", { key });
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveSetting(key: string, value: string): Promise<void> {
|
async saveSetting(key: string, value: string): Promise<void> {
|
||||||
await invoke<void>("save_setting", { key, value });
|
await invoke<void>("save_setting", { key, value });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" stroke="none" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" stroke="none" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g id="Arrow" transform="translate(0 0)">
|
<g id="Arrow" transform="translate(0 0)">
|
||||||
<rect id="Rectangle" width="12" height="8" style="mix-blend-mode:normal;" transform="translate(0 0)" />
|
<rect id="Rectangle" width="12" height="8" style="mix-blend-mode:normal;" transform="translate(0 0)" />
|
||||||
<path id="Path" d="M3.10928 8.76192C3.10928 8.76192 3.10928 0.952386 3.10928 0.952386C3.10928 0.682544 3.1892 0.456512 3.34903 0.274291C3.50887 0.0920672 3.70643 0.000639439 3.94173 3.33786e-06C4.17706 -0.000631809 4.37489 0.0907969 4.53526 0.274291C4.69566 0.457782 4.77529 0.683815 4.77418 0.952386C4.77418 0.952386 4.77418 8.76192 4.77418 8.76192C4.77418 8.76192 6.60566 7 6.60566 7C6.75827 6.8254 6.95253 6.73811 7.18839 6.73811C7.42426 6.73811 7.61849 6.8254 7.77111 7C7.92373 7.1746 8 7.39683 8 7.66667C8 7.93651 7.92373 8.15874 7.77111 8.33334C7.77111 8.33334 4.52444 11.7143 4.52444 11.7143C4.35797 11.9048 4.16372 12 3.94173 12C3.71975 12 3.52551 11.9048 3.35902 11.7143C3.35902 11.7143 0.228926 8.33334 0.228926 8.33334C0.076309 8.15874 0 7.93652 0 7.66668C0 7.39683 0.076309 7.1746 0.228926 7C0.381543 6.8254 0.575782 6.73811 0.811648 6.73811C1.04751 6.73811 1.24175 6.8254 1.39437 7C1.39437 7 3.10928 8.76192 3.10928 8.76192Z" style="fill:#ADA9A1;fill-rule:evenodd;mix-blend-mode:normal;" transform="matrix(0 1 -1 0 12 0)" />
|
<path id="Path" d="M3.10928 8.76192C3.10928 8.76192 3.10928 0.952386 3.10928 0.952386C3.10928 0.682544 3.1892 0.456512 3.34903 0.274291C3.50887 0.0920672 3.70643 0.000639439 3.94173 3.33786e-06C4.17706 -0.000631809 4.37489 0.0907969 4.53526 0.274291C4.69566 0.457782 4.77529 0.683815 4.77418 0.952386C4.77418 0.952386 4.77418 8.76192 4.77418 8.76192C4.77418 8.76192 6.60566 7 6.60566 7C6.75827 6.8254 6.95253 6.73811 7.18839 6.73811C7.42426 6.73811 7.61849 6.8254 7.77111 7C7.92373 7.1746 8 7.39683 8 7.66667C8 7.93651 7.92373 8.15874 7.77111 8.33334C7.77111 8.33334 4.52444 11.7143 4.52444 11.7143C4.35797 11.9048 4.16372 12 3.94173 12C3.71975 12 3.52551 11.9048 3.35902 11.7143C3.35902 11.7143 0.228926 8.33334 0.228926 8.33334C0.076309 8.15874 0 7.93652 0 7.66668C0 7.39683 0.076309 7.1746 0.228926 7C0.381543 6.8254 0.575782 6.73811 0.811648 6.73811C1.04751 6.73811 1.24175 6.8254 1.39437 7C1.39437 7 3.10928 8.76192 3.10928 8.76192Z" style="fill:#ADA9A1;fill-rule:evenodd;mix-blend-mode:normal;" transform="matrix(0 1 -1 0 12 0)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
8
src-tauri/.gitignore
vendored
8
src-tauri/.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
/gen/schemas
|
/gen/schemas
|
||||||
|
|
15618
src-tauri/Cargo.lock
generated
15618
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,3 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
{
|
{
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "enables the default permissions",
|
"description": "enables the default permissions",
|
||||||
"windows": [
|
"windows": [
|
||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:path:default",
|
"core:path:default",
|
||||||
"core:event:default",
|
"core:event:default",
|
||||||
"core:window:default",
|
"core:window:default",
|
||||||
"core:webview:default",
|
"core:webview:default",
|
||||||
"core:app:default",
|
"core:app:default",
|
||||||
"core:resources:default",
|
"core:resources:default",
|
||||||
"core:image:default",
|
"core:image:default",
|
||||||
"core:menu:default",
|
"core:menu:default",
|
||||||
"core:tray:default",
|
"core:tray:default",
|
||||||
"sql:allow-load",
|
"sql:allow-load",
|
||||||
"sql:allow-select",
|
"sql:allow-select",
|
||||||
"sql:allow-execute",
|
"sql:allow-execute",
|
||||||
"autostart:allow-enable",
|
"autostart:allow-enable",
|
||||||
"autostart:allow-disable",
|
"autostart:allow-disable",
|
||||||
"autostart:allow-is-enabled",
|
"autostart:allow-is-enabled",
|
||||||
"os:allow-os-type",
|
"os:allow-os-type",
|
||||||
"core:app:allow-app-hide",
|
"core:app:allow-app-hide",
|
||||||
"core:app:allow-app-show",
|
"core:app:allow-app-show",
|
||||||
"core:window:allow-hide",
|
"core:window:allow-hide",
|
||||||
"core:window:allow-show",
|
"core:window:allow-show",
|
||||||
"core:window:allow-set-focus",
|
"core:window:allow-set-focus",
|
||||||
"core:window:allow-is-focused",
|
"core:window:allow-is-focused",
|
||||||
"core:window:allow-is-visible",
|
"core:window:allow-is-visible",
|
||||||
"fs:allow-read"
|
"fs:allow-read"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,279 +1,279 @@
|
||||||
use tauri_plugin_aptabase::EventTracker;
|
use tauri_plugin_aptabase::EventTracker;
|
||||||
use base64::{ engine::general_purpose::STANDARD, Engine };
|
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||||
// use hyperpolyglot;
|
// use hyperpolyglot;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rdev::{ simulate, EventType, Key };
|
use rdev::{ simulate, EventType, Key };
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::atomic::{ AtomicBool, Ordering };
|
use std::sync::atomic::{ AtomicBool, Ordering };
|
||||||
use std::{ thread, time::Duration };
|
use std::{ thread, time::Duration };
|
||||||
use tauri::{ AppHandle, Emitter, Listener, Manager };
|
use tauri::{ AppHandle, Emitter, Listener, Manager };
|
||||||
use tauri_plugin_clipboard::Clipboard;
|
use tauri_plugin_clipboard::Clipboard;
|
||||||
use tokio::runtime::Runtime as TokioRuntime;
|
use tokio::runtime::Runtime as TokioRuntime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::utils::commands::get_app_info;
|
use crate::utils::commands::get_app_info;
|
||||||
use crate::utils::favicon::fetch_favicon_as_base64;
|
use crate::utils::favicon::fetch_favicon_as_base64;
|
||||||
use crate::utils::types::{ ContentType, HistoryItem };
|
use crate::utils::types::{ ContentType, HistoryItem };
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref IS_PROGRAMMATIC_PASTE: AtomicBool = AtomicBool::new(false);
|
static ref IS_PROGRAMMATIC_PASTE: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn write_and_paste(
|
pub async fn write_and_paste(
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
content: String,
|
content: String,
|
||||||
content_type: String
|
content_type: String
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clipboard = app_handle.state::<Clipboard>();
|
let clipboard = app_handle.state::<Clipboard>();
|
||||||
|
|
||||||
match content_type.as_str() {
|
match content_type.as_str() {
|
||||||
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
"text" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
||||||
"link" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
"link" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
||||||
"color" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
"color" => clipboard.write_text(content).map_err(|e| e.to_string())?,
|
||||||
"image" => {
|
"image" => {
|
||||||
clipboard.write_image_base64(content).map_err(|e| e.to_string())?;
|
clipboard.write_image_base64(content).map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
"files" => {
|
"files" => {
|
||||||
clipboard
|
clipboard
|
||||||
.write_files_uris(
|
.write_files_uris(
|
||||||
content
|
content
|
||||||
.split(", ")
|
.split(", ")
|
||||||
.map(|file| file.to_string())
|
.map(|file| file.to_string())
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
)
|
)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err("Unsupported content type".to_string());
|
return Err("Unsupported content type".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IS_PROGRAMMATIC_PASTE.store(true, Ordering::SeqCst);
|
IS_PROGRAMMATIC_PASTE.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
thread::spawn(|| {
|
thread::spawn(|| {
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let modifier_key = Key::MetaLeft;
|
let modifier_key = Key::MetaLeft;
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
let modifier_key = Key::ControlLeft;
|
let modifier_key = Key::ControlLeft;
|
||||||
|
|
||||||
let events = vec![
|
let events = vec![
|
||||||
EventType::KeyPress(modifier_key),
|
EventType::KeyPress(modifier_key),
|
||||||
EventType::KeyPress(Key::KeyV),
|
EventType::KeyPress(Key::KeyV),
|
||||||
EventType::KeyRelease(Key::KeyV),
|
EventType::KeyRelease(Key::KeyV),
|
||||||
EventType::KeyRelease(modifier_key)
|
EventType::KeyRelease(modifier_key)
|
||||||
];
|
];
|
||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
if let Err(e) = simulate(&event) {
|
if let Err(e) = simulate(&event) {
|
||||||
println!("Simulation error: {:?}", e);
|
println!("Simulation error: {:?}", e);
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(20));
|
thread::sleep(Duration::from_millis(20));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
IS_PROGRAMMATIC_PASTE.store(false, Ordering::SeqCst);
|
IS_PROGRAMMATIC_PASTE.store(false, Ordering::SeqCst);
|
||||||
});
|
});
|
||||||
|
|
||||||
let _ = app_handle.track_event(
|
let _ = app_handle.track_event(
|
||||||
"clipboard_paste",
|
"clipboard_paste",
|
||||||
Some(serde_json::json!({
|
Some(serde_json::json!({
|
||||||
"content_type": content_type
|
"content_type": content_type
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(app: &AppHandle) {
|
pub fn setup(app: &AppHandle) {
|
||||||
let app_handle = app.clone();
|
let app_handle = app.clone();
|
||||||
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
let runtime = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
||||||
|
|
||||||
app_handle.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| {
|
app_handle.clone().listen("plugin:clipboard://clipboard-monitor/update", move |_event| {
|
||||||
let app_handle = app_handle.clone();
|
let app_handle = app_handle.clone();
|
||||||
runtime.block_on(async move {
|
runtime.block_on(async move {
|
||||||
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let clipboard = app_handle.state::<Clipboard>();
|
let clipboard = app_handle.state::<Clipboard>();
|
||||||
let available_types = clipboard.available_types().unwrap();
|
let available_types = clipboard.available_types().unwrap();
|
||||||
|
|
||||||
let (app_name, app_icon) = get_app_info();
|
let (app_name, app_icon) = get_app_info();
|
||||||
|
|
||||||
match get_pool(&app_handle).await {
|
match get_pool(&app_handle).await {
|
||||||
Ok(pool) => {
|
Ok(pool) => {
|
||||||
if available_types.image {
|
if available_types.image {
|
||||||
println!("Handling image change");
|
println!("Handling image change");
|
||||||
if let Ok(image_data) = clipboard.read_image_base64() {
|
if let Ok(image_data) = clipboard.read_image_base64() {
|
||||||
let file_path = save_image_to_file(&app_handle, &image_data).await
|
let file_path = save_image_to_file(&app_handle, &image_data).await
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
.unwrap_or_else(|e| e);
|
.unwrap_or_else(|e| e);
|
||||||
let _ = db::history::add_history_item(
|
let _ = db::history::add_history_item(
|
||||||
app_handle.clone(),
|
app_handle.clone(),
|
||||||
pool,
|
pool,
|
||||||
HistoryItem::new(
|
HistoryItem::new(
|
||||||
app_name,
|
app_name,
|
||||||
ContentType::Image,
|
ContentType::Image,
|
||||||
file_path,
|
file_path,
|
||||||
None,
|
None,
|
||||||
app_icon,
|
app_icon,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
} else if available_types.files {
|
} else if available_types.files {
|
||||||
println!("Handling files change");
|
println!("Handling files change");
|
||||||
if let Ok(files) = clipboard.read_files() {
|
if let Ok(files) = clipboard.read_files() {
|
||||||
for file in files {
|
for file in files {
|
||||||
let _ = db::history::add_history_item(
|
let _ = db::history::add_history_item(
|
||||||
app_handle.clone(),
|
app_handle.clone(),
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
HistoryItem::new(
|
HistoryItem::new(
|
||||||
app_name.clone(),
|
app_name.clone(),
|
||||||
ContentType::File,
|
ContentType::File,
|
||||||
file,
|
file,
|
||||||
None,
|
None,
|
||||||
app_icon.clone(),
|
app_icon.clone(),
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if available_types.text {
|
} else if available_types.text {
|
||||||
println!("Handling text change");
|
println!("Handling text change");
|
||||||
if let Ok(text) = clipboard.read_text() {
|
if let Ok(text) = clipboard.read_text() {
|
||||||
let text = text.to_string();
|
let text = text.to_string();
|
||||||
let url_regex = Regex::new(
|
let url_regex = Regex::new(
|
||||||
r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$"
|
r"^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)$"
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
if url_regex.is_match(&text) {
|
if url_regex.is_match(&text) {
|
||||||
if let Ok(url) = Url::parse(&text) {
|
if let Ok(url) = Url::parse(&text) {
|
||||||
let favicon = match fetch_favicon_as_base64(url).await {
|
let favicon = match fetch_favicon_as_base64(url).await {
|
||||||
Ok(Some(f)) => Some(f),
|
Ok(Some(f)) => Some(f),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = db::history::add_history_item(
|
let _ = db::history::add_history_item(
|
||||||
app_handle.clone(),
|
app_handle.clone(),
|
||||||
pool,
|
pool,
|
||||||
HistoryItem::new(
|
HistoryItem::new(
|
||||||
app_name,
|
app_name,
|
||||||
ContentType::Link,
|
ContentType::Link,
|
||||||
text,
|
text,
|
||||||
favicon,
|
favicon,
|
||||||
app_icon,
|
app_icon,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporarily disabled code detection
|
// Temporarily disabled code detection
|
||||||
/*if let Some(detection) = hyperpolyglot::detect_from_text(&text) {
|
/*if let Some(detection) = hyperpolyglot::detect_from_text(&text) {
|
||||||
let language = match detection {
|
let language = match detection {
|
||||||
hyperpolyglot::Detection::Heuristics(lang) => lang.to_string(),
|
hyperpolyglot::Detection::Heuristics(lang) => lang.to_string(),
|
||||||
_ => detection.language().to_string(),
|
_ => detection.language().to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = db::history::add_history_item(
|
let _ = db::history::add_history_item(
|
||||||
pool,
|
pool,
|
||||||
HistoryItem::new(app_name, ContentType::Code, text, None, app_icon, Some(language))
|
HistoryItem::new(app_name, ContentType::Code, text, None, app_icon, Some(language))
|
||||||
).await;
|
).await;
|
||||||
} else*/ if crate::utils::commands::detect_color(&text) {
|
} else*/ if crate::utils::commands::detect_color(&text) {
|
||||||
let _ = db::history::add_history_item(
|
let _ = db::history::add_history_item(
|
||||||
app_handle.clone(),
|
app_handle.clone(),
|
||||||
pool,
|
pool,
|
||||||
HistoryItem::new(
|
HistoryItem::new(
|
||||||
app_name,
|
app_name,
|
||||||
ContentType::Color,
|
ContentType::Color,
|
||||||
text,
|
text,
|
||||||
None,
|
None,
|
||||||
app_icon,
|
app_icon,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
).await;
|
).await;
|
||||||
} else {
|
} else {
|
||||||
let _ = db::history::add_history_item(
|
let _ = db::history::add_history_item(
|
||||||
app_handle.clone(),
|
app_handle.clone(),
|
||||||
pool,
|
pool,
|
||||||
HistoryItem::new(
|
HistoryItem::new(
|
||||||
app_name,
|
app_name,
|
||||||
ContentType::Text,
|
ContentType::Text,
|
||||||
text.clone(),
|
text.clone(),
|
||||||
None,
|
None,
|
||||||
app_icon,
|
app_icon,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Unknown clipboard content type");
|
println!("Unknown clipboard content type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Failed to get database pool: {}", e);
|
println!("Failed to get database pool: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = app_handle.track_event(
|
let _ = app_handle.track_event(
|
||||||
"clipboard_copied",
|
"clipboard_copied",
|
||||||
Some(
|
Some(
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"content_type": if available_types.image { "image" }
|
"content_type": if available_types.image { "image" }
|
||||||
else if available_types.files { "files" }
|
else if available_types.files { "files" }
|
||||||
else if available_types.text { "text" }
|
else if available_types.text { "text" }
|
||||||
else { "unknown" }
|
else { "unknown" }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_pool(
|
async fn get_pool(
|
||||||
app_handle: &AppHandle
|
app_handle: &AppHandle
|
||||||
) -> Result<tauri::State<'_, SqlitePool>, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<tauri::State<'_, SqlitePool>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
Ok(app_handle.state::<SqlitePool>())
|
Ok(app_handle.state::<SqlitePool>())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
pub fn start_monitor(app_handle: AppHandle) -> Result<(), String> {
|
||||||
let clipboard = app_handle.state::<Clipboard>();
|
let clipboard = app_handle.state::<Clipboard>();
|
||||||
clipboard.start_monitor(app_handle.clone()).map_err(|e| e.to_string())?;
|
clipboard.start_monitor(app_handle.clone()).map_err(|e| e.to_string())?;
|
||||||
app_handle
|
app_handle
|
||||||
.emit("plugin:clipboard://clipboard-monitor/status", true)
|
.emit("plugin:clipboard://clipboard-monitor/status", true)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_image_to_file(
|
async fn save_image_to_file(
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
base64_data: &str
|
base64_data: &str
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let app_data_dir = app_handle.path().app_data_dir().unwrap();
|
let app_data_dir = app_handle.path().app_data_dir().unwrap();
|
||||||
let images_dir = app_data_dir.join("images");
|
let images_dir = app_data_dir.join("images");
|
||||||
fs::create_dir_all(&images_dir)?;
|
fs::create_dir_all(&images_dir)?;
|
||||||
|
|
||||||
let file_name = format!("{}.png", Uuid::new_v4());
|
let file_name = format!("{}.png", Uuid::new_v4());
|
||||||
let file_path = images_dir.join(&file_name);
|
let file_path = images_dir.join(&file_name);
|
||||||
|
|
||||||
let bytes = STANDARD.decode(base64_data)?;
|
let bytes = STANDARD.decode(base64_data)?;
|
||||||
fs::write(&file_path, bytes)?;
|
fs::write(&file_path, bytes)?;
|
||||||
|
|
||||||
Ok(file_path.to_string_lossy().into_owned())
|
Ok(file_path.to_string_lossy().into_owned())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,155 +1,155 @@
|
||||||
use crate::utils::commands::center_window_on_current_monitor;
|
use crate::utils::commands::center_window_on_current_monitor;
|
||||||
use crate::utils::keys::KeyCode;
|
use crate::utils::keys::KeyCode;
|
||||||
use global_hotkey::{
|
use global_hotkey::{
|
||||||
hotkey::{ Code, HotKey, Modifiers },
|
hotkey::{ Code, HotKey, Modifiers },
|
||||||
GlobalHotKeyEvent,
|
GlobalHotKeyEvent,
|
||||||
GlobalHotKeyManager,
|
GlobalHotKeyManager,
|
||||||
HotKeyState,
|
HotKeyState,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{ AppHandle, Manager, Listener };
|
use tauri::{ AppHandle, Manager, Listener };
|
||||||
use tauri_plugin_aptabase::EventTracker;
|
use tauri_plugin_aptabase::EventTracker;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct HotkeyState {
|
struct HotkeyState {
|
||||||
manager: Option<GlobalHotKeyManager>,
|
manager: Option<GlobalHotKeyManager>,
|
||||||
registered_hotkey: Option<HotKey>,
|
registered_hotkey: Option<HotKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for HotkeyState {}
|
unsafe impl Send for HotkeyState {}
|
||||||
|
|
||||||
pub fn setup(app_handle: tauri::AppHandle) {
|
pub fn setup(app_handle: tauri::AppHandle) {
|
||||||
let state = Arc::new(Mutex::new(HotkeyState::default()));
|
let state = Arc::new(Mutex::new(HotkeyState::default()));
|
||||||
let manager = match GlobalHotKeyManager::new() {
|
let manager = match GlobalHotKeyManager::new() {
|
||||||
Ok(manager) => manager,
|
Ok(manager) => manager,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Failed to initialize hotkey manager: {:?}", err);
|
eprintln!("Failed to initialize hotkey manager: {:?}", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut hotkey_state = state.lock();
|
let mut hotkey_state = state.lock();
|
||||||
hotkey_state.manager = Some(manager);
|
hotkey_state.manager = Some(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rt = app_handle.state::<tokio::runtime::Runtime>();
|
let rt = app_handle.state::<tokio::runtime::Runtime>();
|
||||||
let initial_keybind = rt
|
let initial_keybind = rt
|
||||||
.block_on(crate::db::settings::get_keybind(app_handle.clone()))
|
.block_on(crate::db::settings::get_keybind(app_handle.clone()))
|
||||||
.expect("Failed to get initial keybind");
|
.expect("Failed to get initial keybind");
|
||||||
|
|
||||||
if let Err(e) = register_shortcut(&state, &initial_keybind) {
|
if let Err(e) = register_shortcut(&state, &initial_keybind) {
|
||||||
eprintln!("Error registering initial shortcut: {:?}", e);
|
eprintln!("Error registering initial shortcut: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let state_clone = Arc::clone(&state);
|
let state_clone = Arc::clone(&state);
|
||||||
app_handle.listen("update-shortcut", move |event| {
|
app_handle.listen("update-shortcut", move |event| {
|
||||||
let payload_str = event.payload().replace("\\\"", "\"");
|
let payload_str = event.payload().replace("\\\"", "\"");
|
||||||
let trimmed_str = payload_str.trim_matches('"');
|
let trimmed_str = payload_str.trim_matches('"');
|
||||||
unregister_current_hotkey(&state_clone);
|
unregister_current_hotkey(&state_clone);
|
||||||
|
|
||||||
let payload: Vec<String> = serde_json::from_str(trimmed_str).unwrap_or_default();
|
let payload: Vec<String> = serde_json::from_str(trimmed_str).unwrap_or_default();
|
||||||
if let Err(e) = register_shortcut(&state_clone, &payload) {
|
if let Err(e) = register_shortcut(&state_clone, &payload) {
|
||||||
eprintln!("Error re-registering shortcut: {:?}", e);
|
eprintln!("Error re-registering shortcut: {:?}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let state_clone = Arc::clone(&state);
|
let state_clone = Arc::clone(&state);
|
||||||
app_handle.listen("save_keybind", move |event| {
|
app_handle.listen("save_keybind", move |event| {
|
||||||
let payload_str = event.payload().to_string();
|
let payload_str = event.payload().to_string();
|
||||||
unregister_current_hotkey(&state_clone);
|
unregister_current_hotkey(&state_clone);
|
||||||
|
|
||||||
let payload: Vec<String> = serde_json::from_str(&payload_str).unwrap_or_default();
|
let payload: Vec<String> = serde_json::from_str(&payload_str).unwrap_or_default();
|
||||||
if let Err(e) = register_shortcut(&state_clone, &payload) {
|
if let Err(e) = register_shortcut(&state_clone, &payload) {
|
||||||
eprintln!("Error registering saved shortcut: {:?}", e);
|
eprintln!("Error registering saved shortcut: {:?}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setup_hotkey_receiver(app_handle);
|
setup_hotkey_receiver(app_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_hotkey_receiver(app_handle: AppHandle) {
|
fn setup_hotkey_receiver(app_handle: AppHandle) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
match GlobalHotKeyEvent::receiver().recv() {
|
match GlobalHotKeyEvent::receiver().recv() {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
if event.state == HotKeyState::Released {
|
if event.state == HotKeyState::Released {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
handle_hotkey_event(&app_handle);
|
handle_hotkey_event(&app_handle);
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("Error receiving hotkey event: {:?}", e),
|
Err(e) => eprintln!("Error receiving hotkey event: {:?}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unregister_current_hotkey(state: &Arc<Mutex<HotkeyState>>) {
|
fn unregister_current_hotkey(state: &Arc<Mutex<HotkeyState>>) {
|
||||||
let mut hotkey_state = state.lock();
|
let mut hotkey_state = state.lock();
|
||||||
if let Some(old_hotkey) = hotkey_state.registered_hotkey.take() {
|
if let Some(old_hotkey) = hotkey_state.registered_hotkey.take() {
|
||||||
if let Some(manager) = &hotkey_state.manager {
|
if let Some(manager) = &hotkey_state.manager {
|
||||||
let _ = manager.unregister(old_hotkey);
|
let _ = manager.unregister(old_hotkey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_shortcut(state: &Arc<Mutex<HotkeyState>>, shortcut: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
fn register_shortcut(state: &Arc<Mutex<HotkeyState>>, shortcut: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let hotkey = parse_hotkey(shortcut)?;
|
let hotkey = parse_hotkey(shortcut)?;
|
||||||
let mut hotkey_state = state.lock();
|
let mut hotkey_state = state.lock();
|
||||||
|
|
||||||
if let Some(manager) = &hotkey_state.manager {
|
if let Some(manager) = &hotkey_state.manager {
|
||||||
manager.register(hotkey.clone())?;
|
manager.register(hotkey.clone())?;
|
||||||
hotkey_state.registered_hotkey = Some(hotkey);
|
hotkey_state.registered_hotkey = Some(hotkey);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("Hotkey manager not initialized".into())
|
Err("Hotkey manager not initialized".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_hotkey(shortcut: &[String]) -> Result<HotKey, Box<dyn std::error::Error>> {
|
fn parse_hotkey(shortcut: &[String]) -> Result<HotKey, Box<dyn std::error::Error>> {
|
||||||
let mut modifiers = Modifiers::empty();
|
let mut modifiers = Modifiers::empty();
|
||||||
let mut code = None;
|
let mut code = None;
|
||||||
|
|
||||||
for part in shortcut {
|
for part in shortcut {
|
||||||
match part.as_str() {
|
match part.as_str() {
|
||||||
"ControlLeft" => modifiers |= Modifiers::CONTROL,
|
"ControlLeft" => modifiers |= Modifiers::CONTROL,
|
||||||
"AltLeft" => modifiers |= Modifiers::ALT,
|
"AltLeft" => modifiers |= Modifiers::ALT,
|
||||||
"ShiftLeft" => modifiers |= Modifiers::SHIFT,
|
"ShiftLeft" => modifiers |= Modifiers::SHIFT,
|
||||||
"MetaLeft" => modifiers |= Modifiers::META,
|
"MetaLeft" => modifiers |= Modifiers::META,
|
||||||
key => code = Some(Code::from(KeyCode::from_str(key)?)),
|
key => code = Some(Code::from(KeyCode::from_str(key)?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_code = code.ok_or_else(|| "No valid key code found".to_string())?;
|
let key_code = code.ok_or_else(|| "No valid key code found".to_string())?;
|
||||||
Ok(HotKey::new(Some(modifiers), key_code))
|
Ok(HotKey::new(Some(modifiers), key_code))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_hotkey_event(app_handle: &AppHandle) {
|
fn handle_hotkey_event(app_handle: &AppHandle) {
|
||||||
let window = app_handle.get_webview_window("main").unwrap();
|
let window = app_handle.get_webview_window("main").unwrap();
|
||||||
if window.is_visible().unwrap() {
|
if window.is_visible().unwrap() {
|
||||||
window.hide().unwrap();
|
window.hide().unwrap();
|
||||||
} else {
|
} else {
|
||||||
window.set_always_on_top(true).unwrap();
|
window.set_always_on_top(true).unwrap();
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
window.set_focus().unwrap();
|
window.set_focus().unwrap();
|
||||||
|
|
||||||
let window_clone = window.clone();
|
let window_clone = window.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
window_clone.set_always_on_top(false).unwrap();
|
window_clone.set_always_on_top(false).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
center_window_on_current_monitor(&window);
|
center_window_on_current_monitor(&window);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = app_handle.track_event(
|
let _ = app_handle.track_event(
|
||||||
"hotkey_triggered",
|
"hotkey_triggered",
|
||||||
Some(
|
Some(
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"action": if window.is_visible().unwrap() { "hide" } else { "show" }
|
"action": if window.is_visible().unwrap() { "hide" } else { "show" }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
pub mod hotkeys;
|
pub mod hotkeys;
|
||||||
pub mod tray;
|
pub mod tray;
|
||||||
pub mod updater;
|
pub mod updater;
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
use tauri::{ menu::{ MenuBuilder, MenuItemBuilder }, tray::TrayIconBuilder, Emitter, Manager };
|
use tauri::{ menu::{ MenuBuilder, MenuItemBuilder }, tray::TrayIconBuilder, Emitter, Manager };
|
||||||
use tauri_plugin_aptabase::EventTracker;
|
use tauri_plugin_aptabase::EventTracker;
|
||||||
|
|
||||||
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let window = app.get_webview_window("main").unwrap();
|
let window = app.get_webview_window("main").unwrap();
|
||||||
let is_visible = window.is_visible().unwrap();
|
let is_visible = window.is_visible().unwrap();
|
||||||
let _ = app.track_event(
|
let _ = app.track_event(
|
||||||
"tray_toggle",
|
"tray_toggle",
|
||||||
Some(serde_json::json!({
|
Some(serde_json::json!({
|
||||||
"action": if is_visible { "hide" } else { "show" }
|
"action": if is_visible { "hide" } else { "show" }
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
|
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
|
||||||
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
|
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
|
||||||
|
|
||||||
let _tray = TrayIconBuilder::new()
|
let _tray = TrayIconBuilder::new()
|
||||||
.menu(
|
.menu(
|
||||||
&MenuBuilder::new(app)
|
&MenuBuilder::new(app)
|
||||||
.items(&[&MenuItemBuilder::with_id("app_name", "Qopy").enabled(false).build(app)?])
|
.items(&[&MenuItemBuilder::with_id("app_name", "Qopy").enabled(false).build(app)?])
|
||||||
.items(&[&MenuItemBuilder::with_id("show", "Show/Hide").build(app)?])
|
.items(&[&MenuItemBuilder::with_id("show", "Show/Hide").build(app)?])
|
||||||
.items(&[&MenuItemBuilder::with_id("settings", "Settings").build(app)?])
|
.items(&[&MenuItemBuilder::with_id("settings", "Settings").build(app)?])
|
||||||
.items(&[&MenuItemBuilder::with_id("quit", "Quit").build(app)?])
|
.items(&[&MenuItemBuilder::with_id("quit", "Quit").build(app)?])
|
||||||
.build()?
|
.build()?
|
||||||
)
|
)
|
||||||
.on_menu_event(move |_app, event| {
|
.on_menu_event(move |_app, event| {
|
||||||
match event.id().as_ref() {
|
match event.id().as_ref() {
|
||||||
"quit" => {
|
"quit" => {
|
||||||
let _ = _app.track_event("app_quit", None);
|
let _ = _app.track_event("app_quit", None);
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
"show" => {
|
"show" => {
|
||||||
let _ = _app.track_event(
|
let _ = _app.track_event(
|
||||||
"tray_toggle",
|
"tray_toggle",
|
||||||
Some(
|
Some(
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"action": if is_visible { "hide" } else { "show" }
|
"action": if is_visible { "hide" } else { "show" }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
let is_visible = window.is_visible().unwrap();
|
let is_visible = window.is_visible().unwrap();
|
||||||
if is_visible {
|
if is_visible {
|
||||||
window.hide().unwrap();
|
window.hide().unwrap();
|
||||||
} else {
|
} else {
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
window.set_focus().unwrap();
|
window.set_focus().unwrap();
|
||||||
}
|
}
|
||||||
window.emit("main_route", ()).unwrap();
|
window.emit("main_route", ()).unwrap();
|
||||||
}
|
}
|
||||||
"settings" => {
|
"settings" => {
|
||||||
let _ = _app.track_event("tray_settings", None);
|
let _ = _app.track_event("tray_settings", None);
|
||||||
window.emit("settings", ()).unwrap();
|
window.emit("settings", ()).unwrap();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.icon(icon)
|
.icon(icon)
|
||||||
.build(app)?;
|
.build(app)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +1,94 @@
|
||||||
use tauri::{ async_runtime, AppHandle, Manager };
|
use tauri::{ async_runtime, AppHandle, Manager };
|
||||||
use tauri_plugin_dialog::{ DialogExt, MessageDialogButtons, MessageDialogKind };
|
use tauri_plugin_dialog::{ DialogExt, MessageDialogButtons, MessageDialogKind };
|
||||||
use tauri_plugin_updater::UpdaterExt;
|
use tauri_plugin_updater::UpdaterExt;
|
||||||
|
|
||||||
pub async fn check_for_updates(app: AppHandle, prompted: bool) {
|
pub async fn check_for_updates(app: AppHandle, prompted: bool) {
|
||||||
println!("Checking for updates...");
|
println!("Checking for updates...");
|
||||||
|
|
||||||
let updater = app.updater().unwrap();
|
let updater = app.updater().unwrap();
|
||||||
let response = updater.check().await;
|
let response = updater.check().await;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(Some(update)) => {
|
Ok(Some(update)) => {
|
||||||
let cur_ver = &update.current_version;
|
let cur_ver = &update.current_version;
|
||||||
let new_ver = &update.version;
|
let new_ver = &update.version;
|
||||||
let mut msg = String::new();
|
let mut msg = String::new();
|
||||||
msg.extend([
|
msg.extend([
|
||||||
&format!("{cur_ver} -> {new_ver}\n\n"),
|
&format!("{cur_ver} -> {new_ver}\n\n"),
|
||||||
"Would you like to install it now?",
|
"Would you like to install it now?",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let window = app.get_webview_window("main").unwrap();
|
let window = app.get_webview_window("main").unwrap();
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
window.set_focus().unwrap();
|
window.set_focus().unwrap();
|
||||||
|
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(msg)
|
.message(msg)
|
||||||
.title("Qopy Update Available")
|
.title("Qopy Update Available")
|
||||||
.buttons(
|
.buttons(
|
||||||
MessageDialogButtons::OkCancelCustom(
|
MessageDialogButtons::OkCancelCustom(
|
||||||
String::from("Install"),
|
String::from("Install"),
|
||||||
String::from("Cancel")
|
String::from("Cancel")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.show(move |response| {
|
.show(move |response| {
|
||||||
if !response {
|
if !response {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
async_runtime::spawn(async move {
|
async_runtime::spawn(async move {
|
||||||
match
|
match
|
||||||
update.download_and_install(
|
update.download_and_install(
|
||||||
|_, _| {},
|
|_, _| {},
|
||||||
|| {}
|
|| {}
|
||||||
).await
|
).await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(
|
.message(
|
||||||
"Update installed successfully. The application needs to restart to apply the changes."
|
"Update installed successfully. The application needs to restart to apply the changes."
|
||||||
)
|
)
|
||||||
.title("Qopy Update Installed")
|
.title("Qopy Update Installed")
|
||||||
.buttons(
|
.buttons(
|
||||||
MessageDialogButtons::OkCancelCustom(
|
MessageDialogButtons::OkCancelCustom(
|
||||||
String::from("Restart"),
|
String::from("Restart"),
|
||||||
String::from("Cancel")
|
String::from("Cancel")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.show(move |response| {
|
.show(move |response| {
|
||||||
if response {
|
if response {
|
||||||
app.restart();
|
app.restart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error installing new update: {:?}", e);
|
println!("Error installing new update: {:?}", e);
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message(
|
.message(
|
||||||
"Failed to install new update. The new update can be downloaded from Github"
|
"Failed to install new update. The new update can be downloaded from Github"
|
||||||
)
|
)
|
||||||
.kind(MessageDialogKind::Error)
|
.kind(MessageDialogKind::Error)
|
||||||
.show(|_| {});
|
.show(|_| {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
println!("No updates available.");
|
println!("No updates available.");
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if prompted {
|
if prompted {
|
||||||
let window = app.get_webview_window("main").unwrap();
|
let window = app.get_webview_window("main").unwrap();
|
||||||
window.show().unwrap();
|
window.show().unwrap();
|
||||||
window.set_focus().unwrap();
|
window.set_focus().unwrap();
|
||||||
|
|
||||||
app.dialog()
|
app.dialog()
|
||||||
.message("No updates available.")
|
.message("No updates available.")
|
||||||
.title("Qopy Update Check")
|
.title("Qopy Update Check")
|
||||||
.show(|_| {});
|
.show(|_| {});
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("No updates available. {}", e.to_string());
|
println!("No updates available. {}", e.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +1,107 @@
|
||||||
use include_dir::{ include_dir, Dir };
|
use include_dir::{ include_dir, Dir };
|
||||||
use sqlx::sqlite::{ SqlitePool, SqlitePoolOptions };
|
use sqlx::sqlite::{ SqlitePool, SqlitePoolOptions };
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tokio::runtime::Runtime as TokioRuntime;
|
use tokio::runtime::Runtime as TokioRuntime;
|
||||||
|
|
||||||
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/db/migrations");
|
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/db/migrations");
|
||||||
|
|
||||||
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rt = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
let rt = TokioRuntime::new().expect("Failed to create Tokio runtime");
|
||||||
app.manage(rt);
|
app.manage(rt);
|
||||||
|
|
||||||
let rt = app.state::<TokioRuntime>();
|
let rt = app.state::<TokioRuntime>();
|
||||||
|
|
||||||
let app_data_dir = app.path().app_data_dir().unwrap();
|
let app_data_dir = app.path().app_data_dir().unwrap();
|
||||||
fs::create_dir_all(&app_data_dir).expect("Failed to create app data directory");
|
fs::create_dir_all(&app_data_dir).expect("Failed to create app data directory");
|
||||||
|
|
||||||
let db_path = app_data_dir.join("data.db");
|
let db_path = app_data_dir.join("data.db");
|
||||||
let is_new_db = !db_path.exists();
|
let is_new_db = !db_path.exists();
|
||||||
if is_new_db {
|
if is_new_db {
|
||||||
fs::File::create(&db_path).expect("Failed to create database file");
|
fs::File::create(&db_path).expect("Failed to create database file");
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
||||||
let pool = rt.block_on(async {
|
let pool = rt.block_on(async {
|
||||||
SqlitePoolOptions::new()
|
SqlitePoolOptions::new()
|
||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect(&db_url).await
|
.connect(&db_url).await
|
||||||
.expect("Failed to create pool")
|
.expect("Failed to create pool")
|
||||||
});
|
});
|
||||||
|
|
||||||
app.manage(pool.clone());
|
app.manage(pool.clone());
|
||||||
|
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
apply_migrations(&pool).await?;
|
apply_migrations(&pool).await?;
|
||||||
if is_new_db {
|
if is_new_db {
|
||||||
if let Err(e) = super::history::initialize_history(&pool).await {
|
if let Err(e) = super::history::initialize_history(&pool).await {
|
||||||
eprintln!("Failed to initialize history: {}", e);
|
eprintln!("Failed to initialize history: {}", e);
|
||||||
}
|
}
|
||||||
if let Err(e) = super::settings::initialize_settings(&pool).await {
|
if let Err(e) = super::settings::initialize_settings(&pool).await {
|
||||||
eprintln!("Failed to initialize settings: {}", e);
|
eprintln!("Failed to initialize settings: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok::<(), Box<dyn std::error::Error>>(())
|
Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
async fn apply_migrations(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
sqlx
|
sqlx
|
||||||
::query(
|
::query(
|
||||||
"CREATE TABLE IF NOT EXISTS schema_version (
|
"CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
version INTEGER PRIMARY KEY,
|
version INTEGER PRIMARY KEY,
|
||||||
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);"
|
);"
|
||||||
)
|
)
|
||||||
.execute(pool).await?;
|
.execute(pool).await?;
|
||||||
|
|
||||||
let current_version: Option<i64> = sqlx
|
let current_version: Option<i64> = sqlx
|
||||||
::query_scalar("SELECT MAX(version) FROM schema_version")
|
::query_scalar("SELECT MAX(version) FROM schema_version")
|
||||||
.fetch_one(pool).await?;
|
.fetch_one(pool).await?;
|
||||||
|
|
||||||
let current_version = current_version.unwrap_or(0);
|
let current_version = current_version.unwrap_or(0);
|
||||||
|
|
||||||
let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR.files()
|
let mut migration_files: Vec<(i64, &str)> = MIGRATIONS_DIR.files()
|
||||||
.filter_map(|file| {
|
.filter_map(|file| {
|
||||||
let file_name = file.path().file_name()?.to_str()?;
|
let file_name = file.path().file_name()?.to_str()?;
|
||||||
if file_name.ends_with(".sql") && file_name.starts_with("v") {
|
if file_name.ends_with(".sql") && file_name.starts_with("v") {
|
||||||
let version: i64 = file_name
|
let version: i64 = file_name
|
||||||
.trim_start_matches("v")
|
.trim_start_matches("v")
|
||||||
.trim_end_matches(".sql")
|
.trim_end_matches(".sql")
|
||||||
.parse()
|
.parse()
|
||||||
.ok()?;
|
.ok()?;
|
||||||
Some((version, file.contents_utf8()?))
|
Some((version, file.contents_utf8()?))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
migration_files.sort_by_key(|(version, _)| *version);
|
migration_files.sort_by_key(|(version, _)| *version);
|
||||||
|
|
||||||
for (version, content) in migration_files {
|
for (version, content) in migration_files {
|
||||||
if version > current_version {
|
if version > current_version {
|
||||||
let statements: Vec<&str> = content
|
let statements: Vec<&str> = content
|
||||||
.split(';')
|
.split(';')
|
||||||
.map(|s| s.trim())
|
.map(|s| s.trim())
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for statement in statements {
|
for statement in statements {
|
||||||
sqlx
|
sqlx
|
||||||
::query(statement)
|
::query(statement)
|
||||||
.execute(pool).await
|
.execute(pool).await
|
||||||
.map_err(|e| format!("Failed to execute migration {}: {}", version, e))?;
|
.map_err(|e| format!("Failed to execute migration {}: {}", version, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlx
|
sqlx
|
||||||
::query("INSERT INTO schema_version (version) VALUES (?)")
|
::query("INSERT INTO schema_version (version) VALUES (?)")
|
||||||
.bind(version)
|
.bind(version)
|
||||||
.execute(pool).await?;
|
.execute(pool).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,226 +1,226 @@
|
||||||
use crate::utils::types::{ ContentType, HistoryItem };
|
use crate::utils::types::{ ContentType, HistoryItem };
|
||||||
use base64::{ engine::general_purpose::STANDARD, Engine };
|
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||||
use rand::{ rng, Rng };
|
use rand::{ rng, Rng };
|
||||||
use rand::distr::Alphanumeric;
|
use rand::distr::Alphanumeric;
|
||||||
use sqlx::{ Row, SqlitePool };
|
use sqlx::{ Row, SqlitePool };
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tauri_plugin_aptabase::EventTracker;
|
use tauri_plugin_aptabase::EventTracker;
|
||||||
use tauri::Emitter;
|
use tauri::Emitter;
|
||||||
|
|
||||||
pub async fn initialize_history(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn initialize_history(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let id: String = rng()
|
let id: String = rng()
|
||||||
.sample_iter(&Alphanumeric)
|
.sample_iter(&Alphanumeric)
|
||||||
.take(16)
|
.take(16)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO history (id, source, content_type, content, timestamp) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)"
|
"INSERT INTO history (id, source, content_type, content, timestamp) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)"
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.bind("System")
|
.bind("System")
|
||||||
.bind("text")
|
.bind("text")
|
||||||
.bind("Welcome to your clipboard history!")
|
.bind("Welcome to your clipboard history!")
|
||||||
.execute(pool).await?;
|
.execute(pool).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<HistoryItem>, String> {
|
pub async fn get_history(pool: tauri::State<'_, SqlitePool>) -> Result<Vec<HistoryItem>, String> {
|
||||||
let rows = sqlx
|
let rows = sqlx
|
||||||
::query(
|
::query(
|
||||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC"
|
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC"
|
||||||
)
|
)
|
||||||
.fetch_all(&*pool).await
|
.fetch_all(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let items = rows
|
let items = rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|row| HistoryItem {
|
.map(|row| HistoryItem {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
source: row.get("source"),
|
source: row.get("source"),
|
||||||
source_icon: row.get("source_icon"),
|
source_icon: row.get("source_icon"),
|
||||||
content_type: ContentType::from(row.get::<String, _>("content_type")),
|
content_type: ContentType::from(row.get::<String, _>("content_type")),
|
||||||
content: row.get("content"),
|
content: row.get("content"),
|
||||||
favicon: row.get("favicon"),
|
favicon: row.get("favicon"),
|
||||||
timestamp: row.get("timestamp"),
|
timestamp: row.get("timestamp"),
|
||||||
language: row.get("language"),
|
language: row.get("language"),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn add_history_item(
|
pub async fn add_history_item(
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
pool: tauri::State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
item: HistoryItem
|
item: HistoryItem
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let (id, source, source_icon, content_type, content, favicon, timestamp, language) =
|
let (id, source, source_icon, content_type, content, favicon, timestamp, language) =
|
||||||
item.to_row();
|
item.to_row();
|
||||||
|
|
||||||
let existing = sqlx
|
let existing = sqlx
|
||||||
::query("SELECT id FROM history WHERE content = ? AND content_type = ?")
|
::query("SELECT id FROM history WHERE content = ? AND content_type = ?")
|
||||||
.bind(&content)
|
.bind(&content)
|
||||||
.bind(&content_type)
|
.bind(&content_type)
|
||||||
.fetch_optional(&*pool).await
|
.fetch_optional(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match existing {
|
match existing {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
sqlx
|
sqlx
|
||||||
::query(
|
::query(
|
||||||
"UPDATE history SET source = ?, source_icon = ?, timestamp = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now'), favicon = ?, language = ? WHERE content = ? AND content_type = ?"
|
"UPDATE history SET source = ?, source_icon = ?, timestamp = strftime('%Y-%m-%dT%H:%M:%f+00:00', 'now'), favicon = ?, language = ? WHERE content = ? AND content_type = ?"
|
||||||
)
|
)
|
||||||
.bind(&source)
|
.bind(&source)
|
||||||
.bind(&source_icon)
|
.bind(&source_icon)
|
||||||
.bind(&favicon)
|
.bind(&favicon)
|
||||||
.bind(&language)
|
.bind(&language)
|
||||||
.bind(&content)
|
.bind(&content)
|
||||||
.bind(&content_type)
|
.bind(&content_type)
|
||||||
.execute(&*pool).await
|
.execute(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
sqlx
|
sqlx
|
||||||
::query(
|
::query(
|
||||||
"INSERT INTO history (id, source, source_icon, content_type, content, favicon, timestamp, language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
"INSERT INTO history (id, source, source_icon, content_type, content, favicon, timestamp, language) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.bind(source)
|
.bind(source)
|
||||||
.bind(source_icon)
|
.bind(source_icon)
|
||||||
.bind(content_type)
|
.bind(content_type)
|
||||||
.bind(content)
|
.bind(content)
|
||||||
.bind(favicon)
|
.bind(favicon)
|
||||||
.bind(timestamp)
|
.bind(timestamp)
|
||||||
.bind(language)
|
.bind(language)
|
||||||
.execute(&*pool).await
|
.execute(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = app_handle.track_event(
|
let _ = app_handle.track_event(
|
||||||
"history_item_added",
|
"history_item_added",
|
||||||
Some(serde_json::json!({
|
Some(serde_json::json!({
|
||||||
"content_type": item.content_type.to_string()
|
"content_type": item.content_type.to_string()
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = app_handle.emit("clipboard-content-updated", ());
|
let _ = app_handle.emit("clipboard-content-updated", ());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn search_history(
|
pub async fn search_history(
|
||||||
pool: tauri::State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
query: String
|
query: String
|
||||||
) -> Result<Vec<HistoryItem>, String> {
|
) -> Result<Vec<HistoryItem>, String> {
|
||||||
if query.trim().is_empty() {
|
if query.trim().is_empty() {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = format!("%{}%", query);
|
let query = format!("%{}%", query);
|
||||||
|
|
||||||
let rows = sqlx
|
let rows = sqlx
|
||||||
::query(
|
::query(
|
||||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language
|
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language
|
||||||
FROM history
|
FROM history
|
||||||
WHERE content LIKE ?
|
WHERE content LIKE ?
|
||||||
ORDER BY timestamp DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT 100"
|
LIMIT 100"
|
||||||
)
|
)
|
||||||
.bind(query)
|
.bind(query)
|
||||||
.fetch_all(&*pool).await
|
.fetch_all(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut items = Vec::with_capacity(rows.len());
|
let mut items = Vec::with_capacity(rows.len());
|
||||||
for row in rows.iter() {
|
for row in rows.iter() {
|
||||||
items.push(HistoryItem {
|
items.push(HistoryItem {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
source: row.get("source"),
|
source: row.get("source"),
|
||||||
source_icon: row.get("source_icon"),
|
source_icon: row.get("source_icon"),
|
||||||
content_type: ContentType::from(row.get::<String, _>("content_type")),
|
content_type: ContentType::from(row.get::<String, _>("content_type")),
|
||||||
content: row.get("content"),
|
content: row.get("content"),
|
||||||
favicon: row.get("favicon"),
|
favicon: row.get("favicon"),
|
||||||
timestamp: row.get("timestamp"),
|
timestamp: row.get("timestamp"),
|
||||||
language: row.get("language"),
|
language: row.get("language"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn load_history_chunk(
|
pub async fn load_history_chunk(
|
||||||
pool: tauri::State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
offset: i64,
|
offset: i64,
|
||||||
limit: i64
|
limit: i64
|
||||||
) -> Result<Vec<HistoryItem>, String> {
|
) -> Result<Vec<HistoryItem>, String> {
|
||||||
let rows = sqlx
|
let rows = sqlx
|
||||||
::query(
|
::query(
|
||||||
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
"SELECT id, source, source_icon, content_type, content, favicon, timestamp, language FROM history ORDER BY timestamp DESC LIMIT ? OFFSET ?"
|
||||||
)
|
)
|
||||||
.bind(limit)
|
.bind(limit)
|
||||||
.bind(offset)
|
.bind(offset)
|
||||||
.fetch_all(&*pool).await
|
.fetch_all(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let items = rows
|
let items = rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|row| HistoryItem {
|
.map(|row| HistoryItem {
|
||||||
id: row.get("id"),
|
id: row.get("id"),
|
||||||
source: row.get("source"),
|
source: row.get("source"),
|
||||||
source_icon: row.get("source_icon"),
|
source_icon: row.get("source_icon"),
|
||||||
content_type: ContentType::from(row.get::<String, _>("content_type")),
|
content_type: ContentType::from(row.get::<String, _>("content_type")),
|
||||||
content: row.get("content"),
|
content: row.get("content"),
|
||||||
favicon: row.get("favicon"),
|
favicon: row.get("favicon"),
|
||||||
timestamp: row.get("timestamp"),
|
timestamp: row.get("timestamp"),
|
||||||
language: row.get("language"),
|
language: row.get("language"),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn delete_history_item(
|
pub async fn delete_history_item(
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
pool: tauri::State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
id: String
|
id: String
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
sqlx
|
sqlx
|
||||||
::query("DELETE FROM history WHERE id = ?")
|
::query("DELETE FROM history WHERE id = ?")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.execute(&*pool).await
|
.execute(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let _ = app_handle.track_event("history_item_deleted", None);
|
let _ = app_handle.track_event("history_item_deleted", None);
|
||||||
let _ = app_handle.emit("clipboard-content-updated", ());
|
let _ = app_handle.emit("clipboard-content-updated", ());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn clear_history(
|
pub async fn clear_history(
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
pool: tauri::State<'_, SqlitePool>
|
pool: tauri::State<'_, SqlitePool>
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
sqlx
|
sqlx
|
||||||
::query("DELETE FROM history")
|
::query("DELETE FROM history")
|
||||||
.execute(&*pool).await
|
.execute(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let _ = app_handle.track_event("history_cleared", None);
|
let _ = app_handle.track_event("history_cleared", None);
|
||||||
let _ = app_handle.emit("clipboard-content-updated", ());
|
let _ = app_handle.emit("clipboard-content-updated", ());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn read_image(filename: String) -> Result<String, String> {
|
pub async fn read_image(filename: String) -> Result<String, String> {
|
||||||
let bytes = fs::read(filename).map_err(|e| e.to_string())?;
|
let bytes = fs::read(filename).map_err(|e| e.to_string())?;
|
||||||
Ok(STANDARD.encode(bytes))
|
Ok(STANDARD.encode(bytes))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
CREATE TABLE IF NOT EXISTS settings (
|
CREATE TABLE IF NOT EXISTS settings (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT NOT NULL
|
value TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS history (
|
CREATE TABLE IF NOT EXISTS history (
|
||||||
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||||
content_type TEXT NOT NULL,
|
content_type TEXT NOT NULL,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
favicon TEXT,
|
favicon TEXT,
|
||||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
|
@ -1,3 +1,3 @@
|
||||||
ALTER TABLE history ADD COLUMN source TEXT DEFAULT 'System' NOT NULL;
|
ALTER TABLE history ADD COLUMN source TEXT DEFAULT 'System' NOT NULL;
|
||||||
ALTER TABLE history ADD COLUMN source_icon TEXT;
|
ALTER TABLE history ADD COLUMN source_icon TEXT;
|
||||||
ALTER TABLE history ADD COLUMN language TEXT;
|
ALTER TABLE history ADD COLUMN language TEXT;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
INSERT INTO settings (key, value) VALUES ('autostart', 'true');
|
INSERT INTO settings (key, value) VALUES ('autostart', 'true');
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
|
@ -1,87 +1,87 @@
|
||||||
use serde::{ Deserialize, Serialize };
|
use serde::{ Deserialize, Serialize };
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tauri::{ Emitter, Manager };
|
use tauri::{ Emitter, Manager };
|
||||||
use tauri_plugin_aptabase::EventTracker;
|
use tauri_plugin_aptabase::EventTracker;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
struct KeybindSetting {
|
struct KeybindSetting {
|
||||||
keybind: Vec<String>,
|
keybind: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn initialize_settings(pool: &SqlitePool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let default_keybind = KeybindSetting {
|
let default_keybind = KeybindSetting {
|
||||||
keybind: vec!["Meta".to_string(), "V".to_string()],
|
keybind: vec!["Meta".to_string(), "V".to_string()],
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&default_keybind)?;
|
let json = serde_json::to_string(&default_keybind)?;
|
||||||
|
|
||||||
sqlx
|
sqlx
|
||||||
::query("INSERT INTO settings (key, value) VALUES ('keybind', ?)")
|
::query("INSERT INTO settings (key, value) VALUES ('keybind', ?)")
|
||||||
.bind(json)
|
.bind(json)
|
||||||
.execute(pool).await?;
|
.execute(pool).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_setting(
|
pub async fn get_setting(
|
||||||
pool: tauri::State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
key: String
|
key: String
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let row = sqlx
|
let row = sqlx
|
||||||
::query("SELECT value FROM settings WHERE key = ?")
|
::query("SELECT value FROM settings WHERE key = ?")
|
||||||
.bind(key)
|
.bind(key)
|
||||||
.fetch_optional(&*pool).await
|
.fetch_optional(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(row.map(|r| r.get("value")).unwrap_or_default())
|
Ok(row.map(|r| r.get("value")).unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn save_setting(
|
pub async fn save_setting(
|
||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
pool: tauri::State<'_, SqlitePool>,
|
pool: tauri::State<'_, SqlitePool>,
|
||||||
key: String,
|
key: String,
|
||||||
value: String
|
value: String
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
sqlx
|
sqlx
|
||||||
::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
|
::query("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
|
||||||
.bind(key.clone())
|
.bind(key.clone())
|
||||||
.bind(value.clone())
|
.bind(value.clone())
|
||||||
.execute(&*pool).await
|
.execute(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let _ = app_handle.track_event(
|
let _ = app_handle.track_event(
|
||||||
"setting_saved",
|
"setting_saved",
|
||||||
Some(serde_json::json!({
|
Some(serde_json::json!({
|
||||||
"key": key
|
"key": key
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
if key == "keybind" {
|
if key == "keybind" {
|
||||||
let _ = app_handle.emit("update-shortcut", &value).map_err(|e| e.to_string())?;
|
let _ = app_handle.emit("update-shortcut", &value).map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
|
pub async fn get_keybind(app_handle: tauri::AppHandle) -> Result<Vec<String>, String> {
|
||||||
let pool = app_handle.state::<SqlitePool>();
|
let pool = app_handle.state::<SqlitePool>();
|
||||||
|
|
||||||
let row = sqlx
|
let row = sqlx
|
||||||
::query("SELECT value FROM settings WHERE key = 'keybind'")
|
::query("SELECT value FROM settings WHERE key = 'keybind'")
|
||||||
.fetch_optional(&*pool).await
|
.fetch_optional(&*pool).await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let json = row
|
let json = row
|
||||||
.map(|r| r.get::<String, _>("value"))
|
.map(|r| r.get::<String, _>("value"))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
serde_json
|
serde_json
|
||||||
::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()])
|
::to_string(&vec!["MetaLeft".to_string(), "KeyV".to_string()])
|
||||||
.expect("Failed to serialize default keybind")
|
.expect("Failed to serialize default keybind")
|
||||||
});
|
});
|
||||||
|
|
||||||
serde_json::from_str::<Vec<String>>(&json).map_err(|e| e.to_string())
|
serde_json::from_str::<Vec<String>>(&json).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,136 +1,136 @@
|
||||||
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
|
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod db;
|
mod db;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tauri_plugin_aptabase::{ EventTracker, InitOptions };
|
use tauri_plugin_aptabase::{ EventTracker, InitOptions };
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_prevent_default::Flags;
|
use tauri_plugin_prevent_default::Flags;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
||||||
let _guard = runtime.enter();
|
let _guard = runtime.enter();
|
||||||
|
|
||||||
tauri::Builder
|
tauri::Builder
|
||||||
::default()
|
::default()
|
||||||
.plugin(tauri_plugin_clipboard::init())
|
.plugin(tauri_plugin_clipboard::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_sql::Builder::default().build())
|
.plugin(tauri_plugin_sql::Builder::default().build())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::default().build())
|
.plugin(tauri_plugin_updater::Builder::default().build())
|
||||||
.plugin(
|
.plugin(
|
||||||
tauri_plugin_aptabase::Builder
|
tauri_plugin_aptabase::Builder
|
||||||
::new("A-SH-8937252746")
|
::new("A-SH-8937252746")
|
||||||
.with_options(InitOptions {
|
.with_options(InitOptions {
|
||||||
host: Some("https://aptabase.pandadev.net".to_string()),
|
host: Some("https://aptabase.pandadev.net".to_string()),
|
||||||
flush_interval: None,
|
flush_interval: None,
|
||||||
})
|
})
|
||||||
.with_panic_hook(
|
.with_panic_hook(
|
||||||
Box::new(|client, info, msg| {
|
Box::new(|client, info, msg| {
|
||||||
let location = info
|
let location = info
|
||||||
.location()
|
.location()
|
||||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||||
.unwrap_or_else(|| "".to_string());
|
.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
let _ = client.track_event(
|
let _ = client.track_event(
|
||||||
"panic",
|
"panic",
|
||||||
Some(
|
Some(
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"info": format!("{} ({})", msg, location),
|
"info": format!("{} ({})", msg, location),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec![])))
|
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec![])))
|
||||||
.plugin(
|
.plugin(
|
||||||
tauri_plugin_prevent_default::Builder
|
tauri_plugin_prevent_default::Builder
|
||||||
::new()
|
::new()
|
||||||
.with_flags(Flags::all().difference(Flags::CONTEXT_MENU))
|
.with_flags(Flags::all().difference(Flags::CONTEXT_MENU))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
|
||||||
|
|
||||||
let app_data_dir = app.path().app_data_dir().unwrap();
|
let app_data_dir = app.path().app_data_dir().unwrap();
|
||||||
utils::logger::init_logger(&app_data_dir).expect("Failed to initialize logger");
|
utils::logger::init_logger(&app_data_dir).expect("Failed to initialize logger");
|
||||||
|
|
||||||
fs::create_dir_all(&app_data_dir).expect("Failed to create app data directory");
|
fs::create_dir_all(&app_data_dir).expect("Failed to create app data directory");
|
||||||
|
|
||||||
let db_path = app_data_dir.join("data.db");
|
let db_path = app_data_dir.join("data.db");
|
||||||
let is_new_db = !db_path.exists();
|
let is_new_db = !db_path.exists();
|
||||||
if is_new_db {
|
if is_new_db {
|
||||||
fs::File::create(&db_path).expect("Failed to create database file");
|
fs::File::create(&db_path).expect("Failed to create database file");
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
let db_url = format!("sqlite:{}", db_path.to_str().unwrap());
|
||||||
|
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
|
|
||||||
let app_handle_clone = app_handle.clone();
|
let app_handle_clone = app_handle.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let pool = SqlitePoolOptions::new()
|
let pool = SqlitePoolOptions::new()
|
||||||
.max_connections(5)
|
.max_connections(5)
|
||||||
.connect(&db_url).await
|
.connect(&db_url).await
|
||||||
.expect("Failed to create pool");
|
.expect("Failed to create pool");
|
||||||
|
|
||||||
app_handle_clone.manage(pool);
|
app_handle_clone.manage(pool);
|
||||||
});
|
});
|
||||||
|
|
||||||
let main_window = app.get_webview_window("main");
|
let main_window = app.get_webview_window("main");
|
||||||
|
|
||||||
let _ = db::database::setup(app);
|
let _ = db::database::setup(app);
|
||||||
api::hotkeys::setup(app_handle.clone());
|
api::hotkeys::setup(app_handle.clone());
|
||||||
api::tray::setup(app)?;
|
api::tray::setup(app)?;
|
||||||
api::clipboard::setup(app.handle());
|
api::clipboard::setup(app.handle());
|
||||||
let _ = api::clipboard::start_monitor(app_handle.clone());
|
let _ = api::clipboard::start_monitor(app_handle.clone());
|
||||||
|
|
||||||
utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap());
|
utils::commands::center_window_on_current_monitor(main_window.as_ref().unwrap());
|
||||||
main_window
|
main_window
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|w| w.hide())
|
.map(|w| w.hide())
|
||||||
.unwrap_or(Ok(()))?;
|
.unwrap_or(Ok(()))?;
|
||||||
|
|
||||||
let _ = app.track_event("app_started", None);
|
let _ = app.track_event("app_started", None);
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
api::updater::check_for_updates(app_handle, false).await;
|
api::updater::check_for_updates(app_handle, false).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.on_window_event(|_app, _event| {
|
.on_window_event(|_app, _event| {
|
||||||
#[cfg(not(dev))]
|
#[cfg(not(dev))]
|
||||||
if let tauri::WindowEvent::Focused(false) = _event {
|
if let tauri::WindowEvent::Focused(false) = _event {
|
||||||
if let Some(window) = _app.get_webview_window("main") {
|
if let Some(window) = _app.get_webview_window("main") {
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.invoke_handler(
|
.invoke_handler(
|
||||||
tauri::generate_handler![
|
tauri::generate_handler![
|
||||||
api::clipboard::write_and_paste,
|
api::clipboard::write_and_paste,
|
||||||
db::history::get_history,
|
db::history::get_history,
|
||||||
db::history::add_history_item,
|
db::history::add_history_item,
|
||||||
db::history::search_history,
|
db::history::search_history,
|
||||||
db::history::load_history_chunk,
|
db::history::load_history_chunk,
|
||||||
db::history::delete_history_item,
|
db::history::delete_history_item,
|
||||||
db::history::clear_history,
|
db::history::clear_history,
|
||||||
db::history::read_image,
|
db::history::read_image,
|
||||||
db::settings::get_setting,
|
db::settings::get_setting,
|
||||||
db::settings::save_setting,
|
db::settings::save_setting,
|
||||||
utils::commands::fetch_page_meta,
|
utils::commands::fetch_page_meta,
|
||||||
utils::commands::get_app_info
|
utils::commands::get_app_info
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
|
@ -1,155 +1,155 @@
|
||||||
use applications::{AppInfoContext, AppInfo, AppTrait, utils::image::RustImage};
|
use applications::{AppInfoContext, AppInfo, AppTrait, utils::image::RustImage};
|
||||||
use base64::{ engine::general_purpose::STANDARD, Engine };
|
use base64::{ engine::general_purpose::STANDARD, Engine };
|
||||||
use image::codecs::png::PngEncoder;
|
use image::codecs::png::PngEncoder;
|
||||||
use tauri::PhysicalPosition;
|
use tauri::PhysicalPosition;
|
||||||
use meta_fetcher;
|
use meta_fetcher;
|
||||||
|
|
||||||
pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
|
pub fn center_window_on_current_monitor(window: &tauri::WebviewWindow) {
|
||||||
if
|
if
|
||||||
let Some(monitor) = window
|
let Some(monitor) = window
|
||||||
.available_monitors()
|
.available_monitors()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|m| {
|
.find(|m| {
|
||||||
let primary_monitor = window
|
let primary_monitor = window
|
||||||
.primary_monitor()
|
.primary_monitor()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.expect("Failed to get primary monitor");
|
.expect("Failed to get primary monitor");
|
||||||
let mouse_position = primary_monitor.position();
|
let mouse_position = primary_monitor.position();
|
||||||
let monitor_position = m.position();
|
let monitor_position = m.position();
|
||||||
let monitor_size = m.size();
|
let monitor_size = m.size();
|
||||||
mouse_position.x >= monitor_position.x &&
|
mouse_position.x >= monitor_position.x &&
|
||||||
mouse_position.x < monitor_position.x + (monitor_size.width as i32) &&
|
mouse_position.x < monitor_position.x + (monitor_size.width as i32) &&
|
||||||
mouse_position.y >= monitor_position.y &&
|
mouse_position.y >= monitor_position.y &&
|
||||||
mouse_position.y < monitor_position.y + (monitor_size.height as i32)
|
mouse_position.y < monitor_position.y + (monitor_size.height as i32)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let monitor_size = monitor.size();
|
let monitor_size = monitor.size();
|
||||||
let window_size = window.outer_size().unwrap();
|
let window_size = window.outer_size().unwrap();
|
||||||
|
|
||||||
let x = ((monitor_size.width as i32) - (window_size.width as i32)) / 2;
|
let x = ((monitor_size.width as i32) - (window_size.width as i32)) / 2;
|
||||||
let y = ((monitor_size.height as i32) - (window_size.height as i32)) / 2;
|
let y = ((monitor_size.height as i32) - (window_size.height as i32)) / 2;
|
||||||
|
|
||||||
window
|
window
|
||||||
.set_position(PhysicalPosition::new(monitor.position().x + x, monitor.position().y + y))
|
.set_position(PhysicalPosition::new(monitor.position().x + x, monitor.position().y + y))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_app_info() -> (String, Option<String>) {
|
pub fn get_app_info() -> (String, Option<String>) {
|
||||||
println!("Getting app info");
|
println!("Getting app info");
|
||||||
let mut ctx = AppInfoContext::new(vec![]);
|
let mut ctx = AppInfoContext::new(vec![]);
|
||||||
println!("Created AppInfoContext");
|
println!("Created AppInfoContext");
|
||||||
|
|
||||||
if let Err(e) = ctx.refresh_apps() {
|
if let Err(e) = ctx.refresh_apps() {
|
||||||
println!("Failed to refresh apps: {:?}", e);
|
println!("Failed to refresh apps: {:?}", e);
|
||||||
return ("System".to_string(), None);
|
return ("System".to_string(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Refreshed apps");
|
println!("Refreshed apps");
|
||||||
|
|
||||||
let result = std::panic::catch_unwind(|| {
|
let result = std::panic::catch_unwind(|| {
|
||||||
match ctx.get_frontmost_application() {
|
match ctx.get_frontmost_application() {
|
||||||
Ok(window) => {
|
Ok(window) => {
|
||||||
println!("Found frontmost application: {}", window.name);
|
println!("Found frontmost application: {}", window.name);
|
||||||
let name = window.name.clone();
|
let name = window.name.clone();
|
||||||
let icon = window
|
let icon = window
|
||||||
.load_icon()
|
.load_icon()
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|i| {
|
.and_then(|i| {
|
||||||
println!("Loading icon for {}", name);
|
println!("Loading icon for {}", name);
|
||||||
i.to_png().ok().map(|png| {
|
i.to_png().ok().map(|png| {
|
||||||
let encoded = STANDARD.encode(png.get_bytes());
|
let encoded = STANDARD.encode(png.get_bytes());
|
||||||
println!("Icon encoded successfully");
|
println!("Icon encoded successfully");
|
||||||
encoded
|
encoded
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
println!("Returning app info: {} with icon: {}", name, icon.is_some());
|
println!("Returning app info: {} with icon: {}", name, icon.is_some());
|
||||||
(name, icon)
|
(name, icon)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Failed to get frontmost application: {:?}", e);
|
println!("Failed to get frontmost application: {:?}", e);
|
||||||
("System".to_string(), None)
|
("System".to_string(), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(info) => info,
|
Ok(info) => info,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
println!("Panic occurred while getting app info");
|
println!("Panic occurred while getting app info");
|
||||||
("System".to_string(), None)
|
("System".to_string(), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _process_icon_to_base64(path: &str) -> Result<String, Box<dyn std::error::Error>> {
|
fn _process_icon_to_base64(path: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let img = image::open(path)?;
|
let img = image::open(path)?;
|
||||||
let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
|
let resized = img.resize(128, 128, image::imageops::FilterType::Lanczos3);
|
||||||
let mut png_buffer = Vec::new();
|
let mut png_buffer = Vec::new();
|
||||||
resized.write_with_encoder(PngEncoder::new(&mut png_buffer))?;
|
resized.write_with_encoder(PngEncoder::new(&mut png_buffer))?;
|
||||||
Ok(STANDARD.encode(png_buffer))
|
Ok(STANDARD.encode(png_buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detect_color(color: &str) -> bool {
|
pub fn detect_color(color: &str) -> bool {
|
||||||
let color = color.trim().to_lowercase();
|
let color = color.trim().to_lowercase();
|
||||||
|
|
||||||
// hex
|
// hex
|
||||||
if color.starts_with('#') && color.len() == color.trim_end_matches(char::is_whitespace).len() {
|
if color.starts_with('#') && color.len() == color.trim_end_matches(char::is_whitespace).len() {
|
||||||
let hex = &color[1..];
|
let hex = &color[1..];
|
||||||
return match hex.len() {
|
return match hex.len() {
|
||||||
3 | 6 | 8 => hex.chars().all(|c| c.is_ascii_hexdigit()),
|
3 | 6 | 8 => hex.chars().all(|c| c.is_ascii_hexdigit()),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// rgb/rgba
|
// rgb/rgba
|
||||||
if
|
if
|
||||||
(color.starts_with("rgb(") || color.starts_with("rgba(")) &&
|
(color.starts_with("rgb(") || color.starts_with("rgba(")) &&
|
||||||
color.ends_with(")") &&
|
color.ends_with(")") &&
|
||||||
!color[..color.len() - 1].contains(")")
|
!color[..color.len() - 1].contains(")")
|
||||||
{
|
{
|
||||||
let values = color
|
let values = color
|
||||||
.trim_start_matches("rgba(")
|
.trim_start_matches("rgba(")
|
||||||
.trim_start_matches("rgb(")
|
.trim_start_matches("rgb(")
|
||||||
.trim_end_matches(')')
|
.trim_end_matches(')')
|
||||||
.split(',')
|
.split(',')
|
||||||
.collect::<Vec<&str>>();
|
.collect::<Vec<&str>>();
|
||||||
|
|
||||||
return match values.len() {
|
return match values.len() {
|
||||||
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
|
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// hsl/hsla
|
// hsl/hsla
|
||||||
if
|
if
|
||||||
(color.starts_with("hsl(") || color.starts_with("hsla(")) &&
|
(color.starts_with("hsl(") || color.starts_with("hsla(")) &&
|
||||||
color.ends_with(")") &&
|
color.ends_with(")") &&
|
||||||
!color[..color.len() - 1].contains(")")
|
!color[..color.len() - 1].contains(")")
|
||||||
{
|
{
|
||||||
let values = color
|
let values = color
|
||||||
.trim_start_matches("hsla(")
|
.trim_start_matches("hsla(")
|
||||||
.trim_start_matches("hsl(")
|
.trim_start_matches("hsl(")
|
||||||
.trim_end_matches(')')
|
.trim_end_matches(')')
|
||||||
.split(',')
|
.split(',')
|
||||||
.collect::<Vec<&str>>();
|
.collect::<Vec<&str>>();
|
||||||
|
|
||||||
return match values.len() {
|
return match values.len() {
|
||||||
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
|
3 | 4 => values.iter().all(|v| v.trim().parse::<f32>().is_ok()),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn fetch_page_meta(url: String) -> Result<(String, Option<String>), String> {
|
pub async fn fetch_page_meta(url: String) -> Result<(String, Option<String>), String> {
|
||||||
let metadata = meta_fetcher
|
let metadata = meta_fetcher
|
||||||
::fetch_metadata(&url)
|
::fetch_metadata(&url)
|
||||||
.map_err(|e| format!("Failed to fetch metadata: {}", e))?;
|
.map_err(|e| format!("Failed to fetch metadata: {}", e))?;
|
||||||
|
|
||||||
Ok((metadata.title.unwrap_or_else(|| "No title found".to_string()), metadata.image))
|
Ok((metadata.title.unwrap_or_else(|| "No title found".to_string()), metadata.image))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
use base64::engine::general_purpose::STANDARD;
|
use base64::engine::general_purpose::STANDARD;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use image::ImageFormat;
|
use image::ImageFormat;
|
||||||
use reqwest;
|
use reqwest;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub async fn fetch_favicon_as_base64(
|
pub async fn fetch_favicon_as_base64(
|
||||||
url: Url
|
url: Url
|
||||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());
|
let favicon_url = format!("https://favicone.com/{}", url.host_str().unwrap());
|
||||||
let response = client.get(&favicon_url).send().await?;
|
let response = client.get(&favicon_url).send().await?;
|
||||||
|
|
||||||
if response.status().is_success() {
|
if response.status().is_success() {
|
||||||
let bytes = response.bytes().await?;
|
let bytes = response.bytes().await?;
|
||||||
let img = image::load_from_memory(&bytes)?;
|
let img = image::load_from_memory(&bytes)?;
|
||||||
let mut png_bytes: Vec<u8> = Vec::new();
|
let mut png_bytes: Vec<u8> = Vec::new();
|
||||||
img.write_to(&mut std::io::Cursor::new(&mut png_bytes), ImageFormat::Png)?;
|
img.write_to(&mut std::io::Cursor::new(&mut png_bytes), ImageFormat::Png)?;
|
||||||
Ok(Some(STANDARD.encode(&png_bytes)))
|
Ok(Some(STANDARD.encode(&png_bytes)))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,120 +1,120 @@
|
||||||
use global_hotkey::hotkey::Code;
|
use global_hotkey::hotkey::Code;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub struct KeyCode(Code);
|
pub struct KeyCode(Code);
|
||||||
|
|
||||||
impl FromStr for KeyCode {
|
impl FromStr for KeyCode {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let code = match s {
|
let code = match s {
|
||||||
"Backquote" => Code::Backquote,
|
"Backquote" => Code::Backquote,
|
||||||
"Backslash" => Code::Backslash,
|
"Backslash" => Code::Backslash,
|
||||||
"BracketLeft" => Code::BracketLeft,
|
"BracketLeft" => Code::BracketLeft,
|
||||||
"BracketRight" => Code::BracketRight,
|
"BracketRight" => Code::BracketRight,
|
||||||
"Comma" => Code::Comma,
|
"Comma" => Code::Comma,
|
||||||
"Digit0" => Code::Digit0,
|
"Digit0" => Code::Digit0,
|
||||||
"Digit1" => Code::Digit1,
|
"Digit1" => Code::Digit1,
|
||||||
"Digit2" => Code::Digit2,
|
"Digit2" => Code::Digit2,
|
||||||
"Digit3" => Code::Digit3,
|
"Digit3" => Code::Digit3,
|
||||||
"Digit4" => Code::Digit4,
|
"Digit4" => Code::Digit4,
|
||||||
"Digit5" => Code::Digit5,
|
"Digit5" => Code::Digit5,
|
||||||
"Digit6" => Code::Digit6,
|
"Digit6" => Code::Digit6,
|
||||||
"Digit7" => Code::Digit7,
|
"Digit7" => Code::Digit7,
|
||||||
"Digit8" => Code::Digit8,
|
"Digit8" => Code::Digit8,
|
||||||
"Digit9" => Code::Digit9,
|
"Digit9" => Code::Digit9,
|
||||||
"Equal" => Code::Equal,
|
"Equal" => Code::Equal,
|
||||||
"KeyA" => Code::KeyA,
|
"KeyA" => Code::KeyA,
|
||||||
"KeyB" => Code::KeyB,
|
"KeyB" => Code::KeyB,
|
||||||
"KeyC" => Code::KeyC,
|
"KeyC" => Code::KeyC,
|
||||||
"KeyD" => Code::KeyD,
|
"KeyD" => Code::KeyD,
|
||||||
"KeyE" => Code::KeyE,
|
"KeyE" => Code::KeyE,
|
||||||
"KeyF" => Code::KeyF,
|
"KeyF" => Code::KeyF,
|
||||||
"KeyG" => Code::KeyG,
|
"KeyG" => Code::KeyG,
|
||||||
"KeyH" => Code::KeyH,
|
"KeyH" => Code::KeyH,
|
||||||
"KeyI" => Code::KeyI,
|
"KeyI" => Code::KeyI,
|
||||||
"KeyJ" => Code::KeyJ,
|
"KeyJ" => Code::KeyJ,
|
||||||
"KeyK" => Code::KeyK,
|
"KeyK" => Code::KeyK,
|
||||||
"KeyL" => Code::KeyL,
|
"KeyL" => Code::KeyL,
|
||||||
"KeyM" => Code::KeyM,
|
"KeyM" => Code::KeyM,
|
||||||
"KeyN" => Code::KeyN,
|
"KeyN" => Code::KeyN,
|
||||||
"KeyO" => Code::KeyO,
|
"KeyO" => Code::KeyO,
|
||||||
"KeyP" => Code::KeyP,
|
"KeyP" => Code::KeyP,
|
||||||
"KeyQ" => Code::KeyQ,
|
"KeyQ" => Code::KeyQ,
|
||||||
"KeyR" => Code::KeyR,
|
"KeyR" => Code::KeyR,
|
||||||
"KeyS" => Code::KeyS,
|
"KeyS" => Code::KeyS,
|
||||||
"KeyT" => Code::KeyT,
|
"KeyT" => Code::KeyT,
|
||||||
"KeyU" => Code::KeyU,
|
"KeyU" => Code::KeyU,
|
||||||
"KeyV" => Code::KeyV,
|
"KeyV" => Code::KeyV,
|
||||||
"KeyW" => Code::KeyW,
|
"KeyW" => Code::KeyW,
|
||||||
"KeyX" => Code::KeyX,
|
"KeyX" => Code::KeyX,
|
||||||
"KeyY" => Code::KeyY,
|
"KeyY" => Code::KeyY,
|
||||||
"KeyZ" => Code::KeyZ,
|
"KeyZ" => Code::KeyZ,
|
||||||
"Minus" => Code::Minus,
|
"Minus" => Code::Minus,
|
||||||
"Period" => Code::Period,
|
"Period" => Code::Period,
|
||||||
"Quote" => Code::Quote,
|
"Quote" => Code::Quote,
|
||||||
"Semicolon" => Code::Semicolon,
|
"Semicolon" => Code::Semicolon,
|
||||||
"Slash" => Code::Slash,
|
"Slash" => Code::Slash,
|
||||||
"Backspace" => Code::Backspace,
|
"Backspace" => Code::Backspace,
|
||||||
"CapsLock" => Code::CapsLock,
|
"CapsLock" => Code::CapsLock,
|
||||||
"Delete" => Code::Delete,
|
"Delete" => Code::Delete,
|
||||||
"Enter" => Code::Enter,
|
"Enter" => Code::Enter,
|
||||||
"Space" => Code::Space,
|
"Space" => Code::Space,
|
||||||
"Tab" => Code::Tab,
|
"Tab" => Code::Tab,
|
||||||
"End" => Code::End,
|
"End" => Code::End,
|
||||||
"Home" => Code::Home,
|
"Home" => Code::Home,
|
||||||
"Insert" => Code::Insert,
|
"Insert" => Code::Insert,
|
||||||
"PageDown" => Code::PageDown,
|
"PageDown" => Code::PageDown,
|
||||||
"PageUp" => Code::PageUp,
|
"PageUp" => Code::PageUp,
|
||||||
"ArrowDown" => Code::ArrowDown,
|
"ArrowDown" => Code::ArrowDown,
|
||||||
"ArrowLeft" => Code::ArrowLeft,
|
"ArrowLeft" => Code::ArrowLeft,
|
||||||
"ArrowRight" => Code::ArrowRight,
|
"ArrowRight" => Code::ArrowRight,
|
||||||
"ArrowUp" => Code::ArrowUp,
|
"ArrowUp" => Code::ArrowUp,
|
||||||
"NumLock" => Code::NumLock,
|
"NumLock" => Code::NumLock,
|
||||||
"Numpad0" => Code::Numpad0,
|
"Numpad0" => Code::Numpad0,
|
||||||
"Numpad1" => Code::Numpad1,
|
"Numpad1" => Code::Numpad1,
|
||||||
"Numpad2" => Code::Numpad2,
|
"Numpad2" => Code::Numpad2,
|
||||||
"Numpad3" => Code::Numpad3,
|
"Numpad3" => Code::Numpad3,
|
||||||
"Numpad4" => Code::Numpad4,
|
"Numpad4" => Code::Numpad4,
|
||||||
"Numpad5" => Code::Numpad5,
|
"Numpad5" => Code::Numpad5,
|
||||||
"Numpad6" => Code::Numpad6,
|
"Numpad6" => Code::Numpad6,
|
||||||
"Numpad7" => Code::Numpad7,
|
"Numpad7" => Code::Numpad7,
|
||||||
"Numpad8" => Code::Numpad8,
|
"Numpad8" => Code::Numpad8,
|
||||||
"Numpad9" => Code::Numpad9,
|
"Numpad9" => Code::Numpad9,
|
||||||
"NumpadAdd" => Code::NumpadAdd,
|
"NumpadAdd" => Code::NumpadAdd,
|
||||||
"NumpadDecimal" => Code::NumpadDecimal,
|
"NumpadDecimal" => Code::NumpadDecimal,
|
||||||
"NumpadDivide" => Code::NumpadDivide,
|
"NumpadDivide" => Code::NumpadDivide,
|
||||||
"NumpadMultiply" => Code::NumpadMultiply,
|
"NumpadMultiply" => Code::NumpadMultiply,
|
||||||
"NumpadSubtract" => Code::NumpadSubtract,
|
"NumpadSubtract" => Code::NumpadSubtract,
|
||||||
"Escape" => Code::Escape,
|
"Escape" => Code::Escape,
|
||||||
"PrintScreen" => Code::PrintScreen,
|
"PrintScreen" => Code::PrintScreen,
|
||||||
"ScrollLock" => Code::ScrollLock,
|
"ScrollLock" => Code::ScrollLock,
|
||||||
"Pause" => Code::Pause,
|
"Pause" => Code::Pause,
|
||||||
"AudioVolumeDown" => Code::AudioVolumeDown,
|
"AudioVolumeDown" => Code::AudioVolumeDown,
|
||||||
"AudioVolumeMute" => Code::AudioVolumeMute,
|
"AudioVolumeMute" => Code::AudioVolumeMute,
|
||||||
"AudioVolumeUp" => Code::AudioVolumeUp,
|
"AudioVolumeUp" => Code::AudioVolumeUp,
|
||||||
"F1" => Code::F1,
|
"F1" => Code::F1,
|
||||||
"F2" => Code::F2,
|
"F2" => Code::F2,
|
||||||
"F3" => Code::F3,
|
"F3" => Code::F3,
|
||||||
"F4" => Code::F4,
|
"F4" => Code::F4,
|
||||||
"F5" => Code::F5,
|
"F5" => Code::F5,
|
||||||
"F6" => Code::F6,
|
"F6" => Code::F6,
|
||||||
"F7" => Code::F7,
|
"F7" => Code::F7,
|
||||||
"F8" => Code::F8,
|
"F8" => Code::F8,
|
||||||
"F9" => Code::F9,
|
"F9" => Code::F9,
|
||||||
"F10" => Code::F10,
|
"F10" => Code::F10,
|
||||||
"F11" => Code::F11,
|
"F11" => Code::F11,
|
||||||
"F12" => Code::F12,
|
"F12" => Code::F12,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("Unknown key code: {}", s));
|
return Err(format!("Unknown key code: {}", s));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(KeyCode(code))
|
Ok(KeyCode(code))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<KeyCode> for Code {
|
impl From<KeyCode> for Code {
|
||||||
fn from(key_code: KeyCode) -> Self {
|
fn from(key_code: KeyCode) -> Self {
|
||||||
key_code.0
|
key_code.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,84 +1,84 @@
|
||||||
use chrono;
|
use chrono;
|
||||||
use log::{ LevelFilter, SetLoggerError };
|
use log::{ LevelFilter, SetLoggerError };
|
||||||
use std::fs::{ File, OpenOptions };
|
use std::fs::{ File, OpenOptions };
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::panic;
|
use std::panic;
|
||||||
|
|
||||||
pub struct FileLogger {
|
pub struct FileLogger {
|
||||||
file: File,
|
file: File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl log::Log for FileLogger {
|
impl log::Log for FileLogger {
|
||||||
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
fn log(&self, record: &log::Record) {
|
||||||
if self.enabled(record.metadata()) {
|
if self.enabled(record.metadata()) {
|
||||||
let mut file = self.file.try_clone().expect("Failed to clone file handle");
|
let mut file = self.file.try_clone().expect("Failed to clone file handle");
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
file,
|
file,
|
||||||
"{} [{:<5}] {}: {} ({}:{})",
|
"{} [{:<5}] {}: {} ({}:{})",
|
||||||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||||
record.level(),
|
record.level(),
|
||||||
record.target(),
|
record.target(),
|
||||||
record.args(),
|
record.args(),
|
||||||
record.file().unwrap_or("unknown"),
|
record.file().unwrap_or("unknown"),
|
||||||
record.line().unwrap_or(0)
|
record.line().unwrap_or(0)
|
||||||
).expect("Failed to write to log file");
|
).expect("Failed to write to log file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) {
|
fn flush(&self) {
|
||||||
self.file.sync_all().expect("Failed to flush log file");
|
self.file.sync_all().expect("Failed to flush log file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_logger(app_data_dir: &std::path::Path) -> Result<(), SetLoggerError> {
|
pub fn init_logger(app_data_dir: &std::path::Path) -> Result<(), SetLoggerError> {
|
||||||
let logs_dir = app_data_dir.join("logs");
|
let logs_dir = app_data_dir.join("logs");
|
||||||
std::fs::create_dir_all(&logs_dir).expect("Failed to create logs directory");
|
std::fs::create_dir_all(&logs_dir).expect("Failed to create logs directory");
|
||||||
|
|
||||||
let log_path = logs_dir.join("app.log");
|
let log_path = logs_dir.join("app.log");
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(&log_path)
|
.open(&log_path)
|
||||||
.expect("Failed to open log file");
|
.expect("Failed to open log file");
|
||||||
|
|
||||||
let panic_file = file.try_clone().expect("Failed to clone file handle");
|
let panic_file = file.try_clone().expect("Failed to clone file handle");
|
||||||
panic::set_hook(
|
panic::set_hook(
|
||||||
Box::new(move |panic_info| {
|
Box::new(move |panic_info| {
|
||||||
let mut file = panic_file.try_clone().expect("Failed to clone file handle");
|
let mut file = panic_file.try_clone().expect("Failed to clone file handle");
|
||||||
|
|
||||||
let location = panic_info
|
let location = panic_info
|
||||||
.location()
|
.location()
|
||||||
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
.map(|loc| format!("{}:{}:{}", loc.file(), loc.line(), loc.column()))
|
||||||
.unwrap_or_else(|| "unknown location".to_string());
|
.unwrap_or_else(|| "unknown location".to_string());
|
||||||
|
|
||||||
let message = match panic_info.payload().downcast_ref::<&str>() {
|
let message = match panic_info.payload().downcast_ref::<&str>() {
|
||||||
Some(s) => *s,
|
Some(s) => *s,
|
||||||
None =>
|
None =>
|
||||||
match panic_info.payload().downcast_ref::<String>() {
|
match panic_info.payload().downcast_ref::<String>() {
|
||||||
Some(s) => s.as_str(),
|
Some(s) => s.as_str(),
|
||||||
None => "Unknown panic message",
|
None => "Unknown panic message",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
file,
|
file,
|
||||||
"{} [PANIC] rust_panic: {} ({})",
|
"{} [PANIC] rust_panic: {} ({})",
|
||||||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||||
message,
|
message,
|
||||||
location
|
location
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let logger = Box::new(FileLogger { file });
|
let logger = Box::new(FileLogger { file });
|
||||||
unsafe {
|
unsafe {
|
||||||
log::set_logger_racy(Box::leak(logger))?;
|
log::set_logger_racy(Box::leak(logger))?;
|
||||||
}
|
}
|
||||||
log::set_max_level(LevelFilter::Debug);
|
log::set_max_level(LevelFilter::Debug);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod favicon;
|
pub mod favicon;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
|
|
|
@ -1,155 +1,155 @@
|
||||||
use chrono::{ DateTime, Utc };
|
use chrono::{ DateTime, Utc };
|
||||||
use serde::{ Deserialize, Serialize };
|
use serde::{ Deserialize, Serialize };
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||||
pub struct HistoryItem {
|
pub struct HistoryItem {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub source_icon: Option<String>,
|
pub source_icon: Option<String>,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub favicon: Option<String>,
|
pub favicon: Option<String>,
|
||||||
pub timestamp: DateTime<Utc>,
|
pub timestamp: DateTime<Utc>,
|
||||||
pub language: Option<String>,
|
pub language: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ContentType {
|
pub enum ContentType {
|
||||||
Text,
|
Text,
|
||||||
Image,
|
Image,
|
||||||
File,
|
File,
|
||||||
Link,
|
Link,
|
||||||
Color,
|
Color,
|
||||||
Code,
|
Code,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct InfoText {
|
pub struct InfoText {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub characters: i32,
|
pub characters: i32,
|
||||||
pub words: i32,
|
pub words: i32,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct InfoImage {
|
pub struct InfoImage {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub dimensions: String,
|
pub dimensions: String,
|
||||||
pub size: i64,
|
pub size: i64,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct InfoFile {
|
pub struct InfoFile {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub filesize: i64,
|
pub filesize: i64,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct InfoLink {
|
pub struct InfoLink {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub characters: i32,
|
pub characters: i32,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct InfoColor {
|
pub struct InfoColor {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub hex: String,
|
pub hex: String,
|
||||||
pub rgb: String,
|
pub rgb: String,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct InfoCode {
|
pub struct InfoCode {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub content_type: ContentType,
|
pub content_type: ContentType,
|
||||||
pub language: String,
|
pub language: String,
|
||||||
pub lines: i32,
|
pub lines: i32,
|
||||||
pub copied: DateTime<Utc>,
|
pub copied: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ContentType {
|
impl fmt::Display for ContentType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ContentType::Text => write!(f, "text"),
|
ContentType::Text => write!(f, "text"),
|
||||||
ContentType::Image => write!(f, "image"),
|
ContentType::Image => write!(f, "image"),
|
||||||
ContentType::File => write!(f, "file"),
|
ContentType::File => write!(f, "file"),
|
||||||
ContentType::Link => write!(f, "link"),
|
ContentType::Link => write!(f, "link"),
|
||||||
ContentType::Color => write!(f, "color"),
|
ContentType::Color => write!(f, "color"),
|
||||||
ContentType::Code => write!(f, "code"),
|
ContentType::Code => write!(f, "code"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for ContentType {
|
impl From<String> for ContentType {
|
||||||
fn from(s: String) -> Self {
|
fn from(s: String) -> Self {
|
||||||
match s.to_lowercase().as_str() {
|
match s.to_lowercase().as_str() {
|
||||||
"text" => ContentType::Text,
|
"text" => ContentType::Text,
|
||||||
"image" => ContentType::Image,
|
"image" => ContentType::Image,
|
||||||
"file" => ContentType::File,
|
"file" => ContentType::File,
|
||||||
"link" => ContentType::Link,
|
"link" => ContentType::Link,
|
||||||
"color" => ContentType::Color,
|
"color" => ContentType::Color,
|
||||||
"code" => ContentType::Code,
|
"code" => ContentType::Code,
|
||||||
_ => ContentType::Text,
|
_ => ContentType::Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HistoryItem {
|
impl HistoryItem {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
source: String,
|
source: String,
|
||||||
content_type: ContentType,
|
content_type: ContentType,
|
||||||
content: String,
|
content: String,
|
||||||
favicon: Option<String>,
|
favicon: Option<String>,
|
||||||
source_icon: Option<String>,
|
source_icon: Option<String>,
|
||||||
language: Option<String>
|
language: Option<String>
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Uuid::new_v4().to_string(),
|
id: Uuid::new_v4().to_string(),
|
||||||
source,
|
source,
|
||||||
source_icon,
|
source_icon,
|
||||||
content_type,
|
content_type,
|
||||||
content,
|
content,
|
||||||
favicon,
|
favicon,
|
||||||
timestamp: Utc::now(),
|
timestamp: Utc::now(),
|
||||||
language,
|
language,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_row(
|
pub fn to_row(
|
||||||
&self
|
&self
|
||||||
) -> (
|
) -> (
|
||||||
String,
|
String,
|
||||||
String,
|
String,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
String,
|
String,
|
||||||
String,
|
String,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
DateTime<Utc>,
|
DateTime<Utc>,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
) {
|
) {
|
||||||
(
|
(
|
||||||
self.id.clone(),
|
self.id.clone(),
|
||||||
self.source.clone(),
|
self.source.clone(),
|
||||||
self.source_icon.clone(),
|
self.source_icon.clone(),
|
||||||
self.content_type.to_string(),
|
self.content_type.to_string(),
|
||||||
self.content.clone(),
|
self.content.clone(),
|
||||||
self.favicon.clone(),
|
self.favicon.clone(),
|
||||||
self.timestamp,
|
self.timestamp,
|
||||||
self.language.clone(),
|
self.language.clone(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
{
|
{
|
||||||
"productName": "Qopy",
|
"productName": "Qopy",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"identifier": "net.pandadev.qopy",
|
"identifier": "net.pandadev.qopy",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
"devUrl": "http://localhost:3000",
|
"devUrl": "http://localhost:3000",
|
||||||
"beforeDevCommand": "pnpm nuxt dev",
|
"beforeDevCommand": "pnpm nuxt dev",
|
||||||
"beforeBuildCommand": "pnpm nuxt generate"
|
"beforeBuildCommand": "pnpm nuxt generate"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "Qopy",
|
"title": "Qopy",
|
||||||
"titleBarStyle": "Overlay",
|
"titleBarStyle": "Overlay",
|
||||||
"fullscreen": false,
|
"fullscreen": false,
|
||||||
"resizable": false,
|
"resizable": false,
|
||||||
"height": 474,
|
"height": 474,
|
||||||
"width": 750,
|
"width": 750,
|
||||||
"minHeight": 474,
|
"minHeight": 474,
|
||||||
"maxHeight": 474,
|
"maxHeight": 474,
|
||||||
"minWidth": 750,
|
"minWidth": 750,
|
||||||
"maxWidth": 750,
|
"maxWidth": 750,
|
||||||
"decorations": false,
|
"decorations": false,
|
||||||
"center": true,
|
"center": true,
|
||||||
"shadow": false,
|
"shadow": false,
|
||||||
"transparent": true,
|
"transparent": true,
|
||||||
"skipTaskbar": true,
|
"skipTaskbar": true,
|
||||||
"alwaysOnTop": true
|
"alwaysOnTop": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": null
|
"csp": null
|
||||||
},
|
},
|
||||||
"withGlobalTauri": true,
|
"withGlobalTauri": true,
|
||||||
"macOSPrivateApi": true
|
"macOSPrivateApi": true
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"createUpdaterArtifacts": true,
|
"createUpdaterArtifacts": true,
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": "all",
|
"targets": "all",
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"category": "DeveloperTool"
|
"category": "DeveloperTool"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDExNDIzNjA1QjE0NjU1OTkKUldTWlZVYXhCVFpDRWNvNmt0UE5lQmZkblEyZGZiZ2tHelJvT2YvNVpLU1RIM1RKZFQrb2tzWWwK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDExNDIzNjA1QjE0NjU1OTkKUldTWlZVYXhCVFpDRWNvNmt0UE5lQmZkblEyZGZiZ2tHelJvT2YvNVpLU1RIM1RKZFQrb2tzWWwK",
|
||||||
"endpoints": ["https://qopy.pandadev.net/"]
|
"endpoints": ["https://qopy.pandadev.net/"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json"
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,185 +1,185 @@
|
||||||
$primary: #2e2d2b;
|
$primary: #2e2d2b;
|
||||||
$accent: #feb453;
|
$accent: #feb453;
|
||||||
$divider: #ffffff0d;
|
$divider: #ffffff0d;
|
||||||
|
|
||||||
$text: #e5dfd5;
|
$text: #e5dfd5;
|
||||||
$text2: #ada9a1;
|
$text2: #ada9a1;
|
||||||
$mutedtext: #78756f;
|
$mutedtext: #78756f;
|
||||||
|
|
||||||
$search-height: 56px;
|
$search-height: 56px;
|
||||||
$sidebar-width: 286px;
|
$sidebar-width: 286px;
|
||||||
$bottom-bar-height: 39px;
|
$bottom-bar-height: 39px;
|
||||||
$info-panel-height: 160px;
|
$info-panel-height: 160px;
|
||||||
$content-view-height: calc(
|
$content-view-height: calc(
|
||||||
100% - $search-height - $info-panel-height - $bottom-bar-height
|
100% - $search-height - $info-panel-height - $bottom-bar-height
|
||||||
);
|
);
|
||||||
|
|
||||||
main {
|
main {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
border: 1px solid $divider;
|
border: 1px solid $divider;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
height: 376px;
|
height: 376px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 14px 8px;
|
padding: 14px 8px;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-width: 286px;
|
min-width: 286px;
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
|
|
||||||
.time-separator {
|
.time-separator {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $text2;
|
color: $text2;
|
||||||
font-family: SFRoundedSemiBold;
|
font-family: SFRoundedSemiBold;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group {
|
.group {
|
||||||
& + .group {
|
& + .group {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-separator {
|
.time-separator {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.results-group {
|
.results-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.favicon,
|
.favicon,
|
||||||
.image,
|
.image,
|
||||||
.icon {
|
.icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: CommitMono !important;
|
font-family: CommitMono !important;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 1;
|
letter-spacing: 1;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
width: 462px;
|
width: 462px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
color: $text;
|
color: $text;
|
||||||
|
|
||||||
&:not(:has(.image)) {
|
&:not(:has(.image)) {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.content-text {
|
span.content-text {
|
||||||
font-family: CommitMono !important;
|
font-family: CommitMono !important;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.information {
|
.information {
|
||||||
min-height: 160px;
|
min-height: 160px;
|
||||||
width: 462px;
|
width: 462px;
|
||||||
border-top: 1px solid $divider;
|
border-top: 1px solid $divider;
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-family: SFRoundedSemiBold;
|
font-family: SFRoundedSemiBold;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 0.6px;
|
letter-spacing: 0.6px;
|
||||||
color: $text;
|
color: $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-content {
|
.info-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.info-row {
|
.info-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
border-bottom: 1px solid $divider;
|
border-bottom: 1px solid $divider;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
color: $text2;
|
color: $text2;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-family: CommitMono;
|
font-family: CommitMono;
|
||||||
color: $text;
|
color: $text;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-left: 32px;
|
margin-left: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 13px;
|
width: 13px;
|
||||||
height: 13px;
|
height: 13px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-loading {
|
.search-loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
|
@ -1,235 +1,235 @@
|
||||||
$primary: #2e2d2b;
|
$primary: #2e2d2b;
|
||||||
$accent: #feb453;
|
$accent: #feb453;
|
||||||
$divider: #ffffff0d;
|
$divider: #ffffff0d;
|
||||||
|
|
||||||
$text: #e5dfd5;
|
$text: #e5dfd5;
|
||||||
$text2: #ada9a1;
|
$text2: #ada9a1;
|
||||||
$mutedtext: #78756f;
|
$mutedtext: #78756f;
|
||||||
|
|
||||||
main {
|
main {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
border: 1px solid $divider;
|
border: 1px solid $divider;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back {
|
.back {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
background-color: $divider;
|
background-color: $divider;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 8px 6px;
|
padding: 8px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $text2;
|
color: $text2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-container {
|
.settings-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-top: 26px;
|
margin-top: 26px;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
|
|
||||||
.settings {
|
.settings {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
margin-left: -26px;
|
margin-left: -26px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
|
|
||||||
.names {
|
.names {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: SFRoundedSemiBold;
|
font-family: SFRoundedSemiBold;
|
||||||
color: $text2;
|
color: $text2;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
color: $mutedtext;
|
color: $mutedtext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.launch {
|
.launch {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid $mutedtext;
|
border: 1px solid $mutedtext;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
&:checked {
|
&:checked {
|
||||||
~ .checkmark {
|
~ .checkmark {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkmark {
|
.checkmark {
|
||||||
height: 14px;
|
height: 14px;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $text2;
|
color: $text2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keybind-input {
|
.keybind-input {
|
||||||
width: min-content;
|
width: min-content;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
border: 1px solid $divider;
|
border: 1px solid $divider;
|
||||||
color: $text2;
|
color: $text2;
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
outline: none;
|
outline: none;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
color: $text2;
|
color: $text2;
|
||||||
font-family: SFRoundedMedium;
|
font-family: SFRoundedMedium;
|
||||||
background-color: $divider;
|
background-color: $divider;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.keybind-input:focus {
|
.keybind-input:focus {
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-keybind {
|
.empty-keybind {
|
||||||
border-color: rgba(255, 82, 82, 0.298);
|
border-color: rgba(255, 82, 82, 0.298);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar {
|
.top-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
border-bottom: 1px solid $divider;
|
border-bottom: 1px solid $divider;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: calc(100vw - 2px);
|
width: calc(100vw - 2px);
|
||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(18px);
|
||||||
background-color: hsla(40, 3%, 16%, 0.8);
|
background-color: hsla(40, 3%, 16%, 0.8);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1px;
|
bottom: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-radius: 0 0 12px 12px;
|
border-radius: 0 0 12px 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-inline: 12px;
|
padding-inline: 12px;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-top: 1px solid $divider;
|
border-top: 1px solid $divider;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $text2;
|
color: $text2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.actions div {
|
.actions div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
width: 2px;
|
width: 2px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
background-color: $divider;
|
background-color: $divider;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $text;
|
color: $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions:hover {
|
.actions:hover {
|
||||||
background-color: $divider;
|
background-color: $divider;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .actions:hover ~ .divider {
|
&:hover .actions:hover ~ .divider {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
"extends": "./.nuxt/tsconfig.json"
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
}
|
}
|
||||||
|
|
52
types/keyboard.d.ts
vendored
52
types/keyboard.d.ts
vendored
|
@ -1,27 +1,27 @@
|
||||||
import type { Key as WaraduKey, useKeyboard } from '@waradu/keyboard';
|
import type { Key as WaraduKey, useKeyboard } from '@waradu/keyboard';
|
||||||
|
|
||||||
declare module '#app' {
|
declare module '#app' {
|
||||||
interface NuxtApp {
|
interface NuxtApp {
|
||||||
$keyboard: {
|
$keyboard: {
|
||||||
listen: ReturnType<typeof useKeyboard>['listen'];
|
listen: ReturnType<typeof useKeyboard>['listen'];
|
||||||
init: ReturnType<typeof useKeyboard>['init'];
|
init: ReturnType<typeof useKeyboard>['init'];
|
||||||
Key: typeof WaraduKey;
|
Key: typeof WaraduKey;
|
||||||
currentOS: string;
|
currentOS: string;
|
||||||
clearAll: () => void;
|
clearAll: () => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
$keyboard: {
|
$keyboard: {
|
||||||
listen: ReturnType<typeof useKeyboard>['listen'];
|
listen: ReturnType<typeof useKeyboard>['listen'];
|
||||||
init: ReturnType<typeof useKeyboard>['init'];
|
init: ReturnType<typeof useKeyboard>['init'];
|
||||||
Key: typeof WaraduKey;
|
Key: typeof WaraduKey;
|
||||||
currentOS: string;
|
currentOS: string;
|
||||||
clearAll: () => void;
|
clearAll: () => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
432
types/keys.ts
432
types/keys.ts
|
@ -1,217 +1,217 @@
|
||||||
export enum KeyValues {
|
export enum KeyValues {
|
||||||
Backquote = 'Backquote',
|
Backquote = 'Backquote',
|
||||||
Backslash = 'Backslash',
|
Backslash = 'Backslash',
|
||||||
BracketLeft = 'BracketLeft',
|
BracketLeft = 'BracketLeft',
|
||||||
BracketRight = 'BracketRight',
|
BracketRight = 'BracketRight',
|
||||||
Comma = 'Comma',
|
Comma = 'Comma',
|
||||||
Digit0 = 'Digit0',
|
Digit0 = 'Digit0',
|
||||||
Digit1 = 'Digit1',
|
Digit1 = 'Digit1',
|
||||||
Digit2 = 'Digit2',
|
Digit2 = 'Digit2',
|
||||||
Digit3 = 'Digit3',
|
Digit3 = 'Digit3',
|
||||||
Digit4 = 'Digit4',
|
Digit4 = 'Digit4',
|
||||||
Digit5 = 'Digit5',
|
Digit5 = 'Digit5',
|
||||||
Digit6 = 'Digit6',
|
Digit6 = 'Digit6',
|
||||||
Digit7 = 'Digit7',
|
Digit7 = 'Digit7',
|
||||||
Digit8 = 'Digit8',
|
Digit8 = 'Digit8',
|
||||||
Digit9 = 'Digit9',
|
Digit9 = 'Digit9',
|
||||||
Equal = 'Equal',
|
Equal = 'Equal',
|
||||||
KeyA = 'KeyA',
|
KeyA = 'KeyA',
|
||||||
KeyB = 'KeyB',
|
KeyB = 'KeyB',
|
||||||
KeyC = 'KeyC',
|
KeyC = 'KeyC',
|
||||||
KeyD = 'KeyD',
|
KeyD = 'KeyD',
|
||||||
KeyE = 'KeyE',
|
KeyE = 'KeyE',
|
||||||
KeyF = 'KeyF',
|
KeyF = 'KeyF',
|
||||||
KeyG = 'KeyG',
|
KeyG = 'KeyG',
|
||||||
KeyH = 'KeyH',
|
KeyH = 'KeyH',
|
||||||
KeyI = 'KeyI',
|
KeyI = 'KeyI',
|
||||||
KeyJ = 'KeyJ',
|
KeyJ = 'KeyJ',
|
||||||
KeyK = 'KeyK',
|
KeyK = 'KeyK',
|
||||||
KeyL = 'KeyL',
|
KeyL = 'KeyL',
|
||||||
KeyM = 'KeyM',
|
KeyM = 'KeyM',
|
||||||
KeyN = 'KeyN',
|
KeyN = 'KeyN',
|
||||||
KeyO = 'KeyO',
|
KeyO = 'KeyO',
|
||||||
KeyP = 'KeyP',
|
KeyP = 'KeyP',
|
||||||
KeyQ = 'KeyQ',
|
KeyQ = 'KeyQ',
|
||||||
KeyR = 'KeyR',
|
KeyR = 'KeyR',
|
||||||
KeyS = 'KeyS',
|
KeyS = 'KeyS',
|
||||||
KeyT = 'KeyT',
|
KeyT = 'KeyT',
|
||||||
KeyU = 'KeyU',
|
KeyU = 'KeyU',
|
||||||
KeyV = 'KeyV',
|
KeyV = 'KeyV',
|
||||||
KeyW = 'KeyW',
|
KeyW = 'KeyW',
|
||||||
KeyX = 'KeyX',
|
KeyX = 'KeyX',
|
||||||
KeyY = 'KeyY',
|
KeyY = 'KeyY',
|
||||||
KeyZ = 'KeyZ',
|
KeyZ = 'KeyZ',
|
||||||
Minus = 'Minus',
|
Minus = 'Minus',
|
||||||
Period = 'Period',
|
Period = 'Period',
|
||||||
Quote = 'Quote',
|
Quote = 'Quote',
|
||||||
Semicolon = 'Semicolon',
|
Semicolon = 'Semicolon',
|
||||||
Slash = 'Slash',
|
Slash = 'Slash',
|
||||||
AltLeft = 'AltLeft',
|
AltLeft = 'AltLeft',
|
||||||
AltRight = 'AltRight',
|
AltRight = 'AltRight',
|
||||||
Backspace = 'Backspace',
|
Backspace = 'Backspace',
|
||||||
CapsLock = 'CapsLock',
|
CapsLock = 'CapsLock',
|
||||||
ContextMenu = 'ContextMenu',
|
ContextMenu = 'ContextMenu',
|
||||||
ControlLeft = 'ControlLeft',
|
ControlLeft = 'ControlLeft',
|
||||||
ControlRight = 'ControlRight',
|
ControlRight = 'ControlRight',
|
||||||
Enter = 'Enter',
|
Enter = 'Enter',
|
||||||
MetaLeft = 'MetaLeft',
|
MetaLeft = 'MetaLeft',
|
||||||
MetaRight = 'MetaRight',
|
MetaRight = 'MetaRight',
|
||||||
ShiftLeft = 'ShiftLeft',
|
ShiftLeft = 'ShiftLeft',
|
||||||
ShiftRight = 'ShiftRight',
|
ShiftRight = 'ShiftRight',
|
||||||
Space = 'Space',
|
Space = 'Space',
|
||||||
Tab = 'Tab',
|
Tab = 'Tab',
|
||||||
Delete = 'Delete',
|
Delete = 'Delete',
|
||||||
End = 'End',
|
End = 'End',
|
||||||
Home = 'Home',
|
Home = 'Home',
|
||||||
Insert = 'Insert',
|
Insert = 'Insert',
|
||||||
PageDown = 'PageDown',
|
PageDown = 'PageDown',
|
||||||
PageUp = 'PageUp',
|
PageUp = 'PageUp',
|
||||||
ArrowDown = 'ArrowDown',
|
ArrowDown = 'ArrowDown',
|
||||||
ArrowLeft = 'ArrowLeft',
|
ArrowLeft = 'ArrowLeft',
|
||||||
ArrowRight = 'ArrowRight',
|
ArrowRight = 'ArrowRight',
|
||||||
ArrowUp = 'ArrowUp',
|
ArrowUp = 'ArrowUp',
|
||||||
NumLock = 'NumLock',
|
NumLock = 'NumLock',
|
||||||
Numpad0 = 'Numpad0',
|
Numpad0 = 'Numpad0',
|
||||||
Numpad1 = 'Numpad1',
|
Numpad1 = 'Numpad1',
|
||||||
Numpad2 = 'Numpad2',
|
Numpad2 = 'Numpad2',
|
||||||
Numpad3 = 'Numpad3',
|
Numpad3 = 'Numpad3',
|
||||||
Numpad4 = 'Numpad4',
|
Numpad4 = 'Numpad4',
|
||||||
Numpad5 = 'Numpad5',
|
Numpad5 = 'Numpad5',
|
||||||
Numpad6 = 'Numpad6',
|
Numpad6 = 'Numpad6',
|
||||||
Numpad7 = 'Numpad7',
|
Numpad7 = 'Numpad7',
|
||||||
Numpad8 = 'Numpad8',
|
Numpad8 = 'Numpad8',
|
||||||
Numpad9 = 'Numpad9',
|
Numpad9 = 'Numpad9',
|
||||||
NumpadAdd = 'NumpadAdd',
|
NumpadAdd = 'NumpadAdd',
|
||||||
NumpadDecimal = 'NumpadDecimal',
|
NumpadDecimal = 'NumpadDecimal',
|
||||||
NumpadDivide = 'NumpadDivide',
|
NumpadDivide = 'NumpadDivide',
|
||||||
NumpadMultiply = 'NumpadMultiply',
|
NumpadMultiply = 'NumpadMultiply',
|
||||||
NumpadSubtract = 'NumpadSubtract',
|
NumpadSubtract = 'NumpadSubtract',
|
||||||
Escape = 'Escape',
|
Escape = 'Escape',
|
||||||
PrintScreen = 'PrintScreen',
|
PrintScreen = 'PrintScreen',
|
||||||
ScrollLock = 'ScrollLock',
|
ScrollLock = 'ScrollLock',
|
||||||
Pause = 'Pause',
|
Pause = 'Pause',
|
||||||
AudioVolumeDown = 'AudioVolumeDown',
|
AudioVolumeDown = 'AudioVolumeDown',
|
||||||
AudioVolumeMute = 'AudioVolumeMute',
|
AudioVolumeMute = 'AudioVolumeMute',
|
||||||
AudioVolumeUp = 'AudioVolumeUp',
|
AudioVolumeUp = 'AudioVolumeUp',
|
||||||
F1 = 'F1',
|
F1 = 'F1',
|
||||||
F2 = 'F2',
|
F2 = 'F2',
|
||||||
F3 = 'F3',
|
F3 = 'F3',
|
||||||
F4 = 'F4',
|
F4 = 'F4',
|
||||||
F5 = 'F5',
|
F5 = 'F5',
|
||||||
F6 = 'F6',
|
F6 = 'F6',
|
||||||
F7 = 'F7',
|
F7 = 'F7',
|
||||||
F8 = 'F8',
|
F8 = 'F8',
|
||||||
F9 = 'F9',
|
F9 = 'F9',
|
||||||
F10 = 'F10',
|
F10 = 'F10',
|
||||||
F11 = 'F11',
|
F11 = 'F11',
|
||||||
F12 = 'F12',
|
F12 = 'F12',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum KeyLabels {
|
export enum KeyLabels {
|
||||||
Backquote = '`',
|
Backquote = '`',
|
||||||
Backslash = '\\',
|
Backslash = '\\',
|
||||||
BracketLeft = '[',
|
BracketLeft = '[',
|
||||||
BracketRight = ']',
|
BracketRight = ']',
|
||||||
Comma = ',',
|
Comma = ',',
|
||||||
Digit0 = '0',
|
Digit0 = '0',
|
||||||
Digit1 = '1',
|
Digit1 = '1',
|
||||||
Digit2 = '2',
|
Digit2 = '2',
|
||||||
Digit3 = '3',
|
Digit3 = '3',
|
||||||
Digit4 = '4',
|
Digit4 = '4',
|
||||||
Digit5 = '5',
|
Digit5 = '5',
|
||||||
Digit6 = '6',
|
Digit6 = '6',
|
||||||
Digit7 = '7',
|
Digit7 = '7',
|
||||||
Digit8 = '8',
|
Digit8 = '8',
|
||||||
Digit9 = '9',
|
Digit9 = '9',
|
||||||
Equal = '=',
|
Equal = '=',
|
||||||
KeyA = 'A',
|
KeyA = 'A',
|
||||||
KeyB = 'B',
|
KeyB = 'B',
|
||||||
KeyC = 'C',
|
KeyC = 'C',
|
||||||
KeyD = 'D',
|
KeyD = 'D',
|
||||||
KeyE = 'E',
|
KeyE = 'E',
|
||||||
KeyF = 'F',
|
KeyF = 'F',
|
||||||
KeyG = 'G',
|
KeyG = 'G',
|
||||||
KeyH = 'H',
|
KeyH = 'H',
|
||||||
KeyI = 'I',
|
KeyI = 'I',
|
||||||
KeyJ = 'J',
|
KeyJ = 'J',
|
||||||
KeyK = 'K',
|
KeyK = 'K',
|
||||||
KeyL = 'L',
|
KeyL = 'L',
|
||||||
KeyM = 'M',
|
KeyM = 'M',
|
||||||
KeyN = 'N',
|
KeyN = 'N',
|
||||||
KeyO = 'O',
|
KeyO = 'O',
|
||||||
KeyP = 'P',
|
KeyP = 'P',
|
||||||
KeyQ = 'Q',
|
KeyQ = 'Q',
|
||||||
KeyR = 'R',
|
KeyR = 'R',
|
||||||
KeyS = 'S',
|
KeyS = 'S',
|
||||||
KeyT = 'T',
|
KeyT = 'T',
|
||||||
KeyU = 'U',
|
KeyU = 'U',
|
||||||
KeyV = 'V',
|
KeyV = 'V',
|
||||||
KeyW = 'W',
|
KeyW = 'W',
|
||||||
KeyX = 'X',
|
KeyX = 'X',
|
||||||
KeyY = 'Y',
|
KeyY = 'Y',
|
||||||
KeyZ = 'Z',
|
KeyZ = 'Z',
|
||||||
Minus = '-',
|
Minus = '-',
|
||||||
Period = '.',
|
Period = '.',
|
||||||
Quote = "'",
|
Quote = "'",
|
||||||
Semicolon = ';',
|
Semicolon = ';',
|
||||||
Slash = '/',
|
Slash = '/',
|
||||||
AltLeft = 'Alt',
|
AltLeft = 'Alt',
|
||||||
AltRight = 'Alt (Right)',
|
AltRight = 'Alt (Right)',
|
||||||
Backspace = 'Backspace',
|
Backspace = 'Backspace',
|
||||||
CapsLock = 'Caps Lock',
|
CapsLock = 'Caps Lock',
|
||||||
ContextMenu = 'Context Menu',
|
ContextMenu = 'Context Menu',
|
||||||
ControlLeft = 'Ctrl',
|
ControlLeft = 'Ctrl',
|
||||||
ControlRight = 'Ctrl (Right)',
|
ControlRight = 'Ctrl (Right)',
|
||||||
Enter = 'Enter',
|
Enter = 'Enter',
|
||||||
MetaLeft = 'Meta',
|
MetaLeft = 'Meta',
|
||||||
MetaRight = 'Meta (Right)',
|
MetaRight = 'Meta (Right)',
|
||||||
ShiftLeft = 'Shift',
|
ShiftLeft = 'Shift',
|
||||||
ShiftRight = 'Shift (Right)',
|
ShiftRight = 'Shift (Right)',
|
||||||
Space = 'Space',
|
Space = 'Space',
|
||||||
Tab = 'Tab',
|
Tab = 'Tab',
|
||||||
Delete = 'Delete',
|
Delete = 'Delete',
|
||||||
End = 'End',
|
End = 'End',
|
||||||
Home = 'Home',
|
Home = 'Home',
|
||||||
Insert = 'Insert',
|
Insert = 'Insert',
|
||||||
PageDown = 'Page Down',
|
PageDown = 'Page Down',
|
||||||
PageUp = 'Page Up',
|
PageUp = 'Page Up',
|
||||||
ArrowDown = '↓',
|
ArrowDown = '↓',
|
||||||
ArrowLeft = '←',
|
ArrowLeft = '←',
|
||||||
ArrowRight = '→',
|
ArrowRight = '→',
|
||||||
ArrowUp = '↑',
|
ArrowUp = '↑',
|
||||||
NumLock = 'Num Lock',
|
NumLock = 'Num Lock',
|
||||||
Numpad0 = 'Numpad 0',
|
Numpad0 = 'Numpad 0',
|
||||||
Numpad1 = 'Numpad 1',
|
Numpad1 = 'Numpad 1',
|
||||||
Numpad2 = 'Numpad 2',
|
Numpad2 = 'Numpad 2',
|
||||||
Numpad3 = 'Numpad 3',
|
Numpad3 = 'Numpad 3',
|
||||||
Numpad4 = 'Numpad 4',
|
Numpad4 = 'Numpad 4',
|
||||||
Numpad5 = 'Numpad 5',
|
Numpad5 = 'Numpad 5',
|
||||||
Numpad6 = 'Numpad 6',
|
Numpad6 = 'Numpad 6',
|
||||||
Numpad7 = 'Numpad 7',
|
Numpad7 = 'Numpad 7',
|
||||||
Numpad8 = 'Numpad 8',
|
Numpad8 = 'Numpad 8',
|
||||||
Numpad9 = 'Numpad 9',
|
Numpad9 = 'Numpad 9',
|
||||||
NumpadAdd = 'Numpad +',
|
NumpadAdd = 'Numpad +',
|
||||||
NumpadDecimal = 'Numpad .',
|
NumpadDecimal = 'Numpad .',
|
||||||
NumpadDivide = 'Numpad /',
|
NumpadDivide = 'Numpad /',
|
||||||
NumpadMultiply = 'Numpad *',
|
NumpadMultiply = 'Numpad *',
|
||||||
NumpadSubtract = 'Numpad -',
|
NumpadSubtract = 'Numpad -',
|
||||||
Escape = 'Esc',
|
Escape = 'Esc',
|
||||||
PrintScreen = 'Print Screen',
|
PrintScreen = 'Print Screen',
|
||||||
ScrollLock = 'Scroll Lock',
|
ScrollLock = 'Scroll Lock',
|
||||||
Pause = 'Pause',
|
Pause = 'Pause',
|
||||||
AudioVolumeDown = 'Volume Down',
|
AudioVolumeDown = 'Volume Down',
|
||||||
AudioVolumeMute = 'Volume Mute',
|
AudioVolumeMute = 'Volume Mute',
|
||||||
AudioVolumeUp = 'Volume Up',
|
AudioVolumeUp = 'Volume Up',
|
||||||
F1 = 'F1',
|
F1 = 'F1',
|
||||||
F2 = 'F2',
|
F2 = 'F2',
|
||||||
F3 = 'F3',
|
F3 = 'F3',
|
||||||
F4 = 'F4',
|
F4 = 'F4',
|
||||||
F5 = 'F5',
|
F5 = 'F5',
|
||||||
F6 = 'F6',
|
F6 = 'F6',
|
||||||
F7 = 'F7',
|
F7 = 'F7',
|
||||||
F8 = 'F8',
|
F8 = 'F8',
|
||||||
F9 = 'F9',
|
F9 = 'F9',
|
||||||
F10 = 'F10',
|
F10 = 'F10',
|
||||||
F11 = 'F11',
|
F11 = 'F11',
|
||||||
F12 = 'F12',
|
F12 = 'F12',
|
||||||
}
|
}
|
232
types/types.ts
232
types/types.ts
|
@ -1,116 +1,116 @@
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
export enum ContentType {
|
export enum ContentType {
|
||||||
Text = "text",
|
Text = "text",
|
||||||
Image = "image",
|
Image = "image",
|
||||||
File = "file",
|
File = "file",
|
||||||
Link = "link",
|
Link = "link",
|
||||||
Color = "color",
|
Color = "color",
|
||||||
Code = "code",
|
Code = "code",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HistoryItem {
|
export class HistoryItem {
|
||||||
id: string;
|
id: string;
|
||||||
source: string;
|
source: string;
|
||||||
source_icon?: string;
|
source_icon?: string;
|
||||||
content_type: ContentType;
|
content_type: ContentType;
|
||||||
content: string;
|
content: string;
|
||||||
favicon?: string;
|
favicon?: string;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
language?: string;
|
language?: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
source: string,
|
source: string,
|
||||||
content_type: ContentType,
|
content_type: ContentType,
|
||||||
content: string,
|
content: string,
|
||||||
favicon?: string,
|
favicon?: string,
|
||||||
source_icon?: string,
|
source_icon?: string,
|
||||||
language?: string
|
language?: string
|
||||||
) {
|
) {
|
||||||
this.id = uuidv4();
|
this.id = uuidv4();
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.source_icon = source_icon;
|
this.source_icon = source_icon;
|
||||||
this.content_type = content_type;
|
this.content_type = content_type;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.favicon = favicon;
|
this.favicon = favicon;
|
||||||
this.timestamp = new Date();
|
this.timestamp = new Date();
|
||||||
this.language = language;
|
this.language = language;
|
||||||
}
|
}
|
||||||
|
|
||||||
toRow(): [
|
toRow(): [
|
||||||
string,
|
string,
|
||||||
string,
|
string,
|
||||||
string | undefined,
|
string | undefined,
|
||||||
string,
|
string,
|
||||||
string,
|
string,
|
||||||
string | undefined,
|
string | undefined,
|
||||||
Date,
|
Date,
|
||||||
string | undefined
|
string | undefined
|
||||||
] {
|
] {
|
||||||
return [
|
return [
|
||||||
this.id,
|
this.id,
|
||||||
this.source,
|
this.source,
|
||||||
this.source_icon,
|
this.source_icon,
|
||||||
this.content_type,
|
this.content_type,
|
||||||
this.content,
|
this.content,
|
||||||
this.favicon,
|
this.favicon,
|
||||||
this.timestamp,
|
this.timestamp,
|
||||||
this.language,
|
this.language,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoText {
|
export interface InfoText {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Text;
|
content_type: ContentType.Text;
|
||||||
characters: number;
|
characters: number;
|
||||||
words: number;
|
words: number;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoImage {
|
export interface InfoImage {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Image;
|
content_type: ContentType.Image;
|
||||||
dimensions: string;
|
dimensions: string;
|
||||||
size: number;
|
size: number;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoFile {
|
export interface InfoFile {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.File;
|
content_type: ContentType.File;
|
||||||
path: string;
|
path: string;
|
||||||
filesize: number;
|
filesize: number;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoLink {
|
export interface InfoLink {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Link;
|
content_type: ContentType.Link;
|
||||||
title?: string;
|
title?: string;
|
||||||
url: string;
|
url: string;
|
||||||
characters: number;
|
characters: number;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoColor {
|
export interface InfoColor {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Color;
|
content_type: ContentType.Color;
|
||||||
hex: string;
|
hex: string;
|
||||||
rgb: string;
|
rgb: string;
|
||||||
hsl: string;
|
hsl: string;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoCode {
|
export interface InfoCode {
|
||||||
source: string;
|
source: string;
|
||||||
content_type: ContentType.Code;
|
content_type: ContentType.Code;
|
||||||
language: string;
|
language: string;
|
||||||
lines: number;
|
lines: number;
|
||||||
copied: Date;
|
copied: Date;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue