Compare commits
6 commits
main
...
springboar
Author | SHA1 | Date | |
---|---|---|---|
386757cbb9 | |||
ab3380b68e | |||
e38c8421f4 | |||
9683b8eafa | |||
29fade983d | |||
70088d37e0 |
4
.gitignore
vendored
|
@ -90,7 +90,6 @@ out
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
# Nuxt.js build / generate output
|
||||||
.nuxt
|
.nuxt
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
# Gatsby files
|
||||||
.cache/
|
.cache/
|
||||||
|
@ -130,3 +129,6 @@ dist
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
# Mock hiding
|
||||||
|
public/mock/*
|
||||||
|
!public/mock/notice.txt
|
||||||
|
|
3
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
1105
Bridge-mock-APIs.d.ts
vendored
Normal file
14
README.md
|
@ -1,3 +1,13 @@
|
||||||
# blhs
|
# SpringBoard-like
|
||||||
|
|
||||||
Bridge Launcher Home Screens I made for myself but published for no reason
|
A Brige Launcher Project inspired by SpringBoard, iOS default launcher,
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
|
||||||
|
*none because still in development*
|
||||||
|
|
||||||
|
# Customizing dock apps
|
||||||
|
1. Open [Dock.vue](src/components/Dock.vue)
|
||||||
|
2. On your phone (or dev env), select the *invisible* text below the icon's label name. Copy it fully.
|
||||||
|
> Note: Thats the **Package name** of the app you want to pin on the dock
|
||||||
|
3. Put the wanted app's package name instead of the default one
|
BIN
dist/assets/bootstrap-icons-BOrJxbIo.woff
vendored
Normal file
BIN
dist/assets/bootstrap-icons-BtvjY1KL.woff2
vendored
Normal file
5
dist/assets/index-_sk3xYAV.css
vendored
Normal file
17
dist/assets/index-mum_jNSN.js
vendored
Normal file
BIN
dist/bundle.zip
vendored
Normal file
BIN
dist/com.tored.bridgelauncher.png
vendored
Normal file
After Width: | Height: | Size: 6.8 KiB |
65
dist/error.svg
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="150 240 165 165"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
sodipodi:docname="dotgrid-25G10-623290.svg"
|
||||||
|
width="165"
|
||||||
|
height="165"
|
||||||
|
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview2"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:clip-to-page="false"
|
||||||
|
inkscape:antialias-rendering="true"
|
||||||
|
showborder="true"
|
||||||
|
inkscape:zoom="3.608628"
|
||||||
|
inkscape:cx="47.38643"
|
||||||
|
inkscape:cy="80.501509"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1014"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<!-- Filled triangle -->
|
||||||
|
<path
|
||||||
|
stroke-width="15.0001"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke="#ff8888"
|
||||||
|
fill="#ff8888"
|
||||||
|
d="M 157.49952,389.99997 H 307.50049 L 232.5,254.99926 Z"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
stroke-width="15"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke="#000000"
|
||||||
|
fill="none"
|
||||||
|
d="m 232.49948,283.49921 v 60"
|
||||||
|
id="path2" />
|
||||||
|
<circle
|
||||||
|
cx="232.56358"
|
||||||
|
cy="371.59991"
|
||||||
|
stroke="rgb(255,136,136)"
|
||||||
|
stroke-width="7.50004"
|
||||||
|
fill="rgb(0,0,0)"
|
||||||
|
id="circle2"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
r="7.5" />
|
||||||
|
<!-- Vertical line -->
|
||||||
|
<!-- Circle below the vertical line -->
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
14
dist/index.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>SpringBoard</title>
|
||||||
|
<script type="module" crossorigin src="/assets/index-mum_jNSN.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/assets/index-_sk3xYAV.css">
|
||||||
|
</head>
|
||||||
|
<body style="background-color:transparent;">
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
dist/vite.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
dist/vue.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
13
index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>SpringBoard</title>
|
||||||
|
</head>
|
||||||
|
<body style="background-color:transparent;">
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "blhs",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build && rm -rf dist/mock",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"zip": "zip -r dist/bundle.zip dist/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bridgelauncher/api-mock": "^0.1.0",
|
||||||
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"fuzzy": "^0.1.3",
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"sass": "^1.86.2",
|
||||||
|
"vite": "^6.2.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.6.1+sha512.40ee09af407fa9fbb5fbfb8e1cb40fbb74c0af0c3e10e9224d7b53c7658528615b2c92450e74cfad91e3a2dcafe3ce4050d80bda71d757756d2ce2b66213e9a3",
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"@parcel/watcher"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
1033
pnpm-lock.yaml
generated
Normal file
BIN
public/com.tored.bridgelauncher.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
65
public/error.svg
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="150 240 165 165"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
sodipodi:docname="dotgrid-25G10-623290.svg"
|
||||||
|
width="165"
|
||||||
|
height="165"
|
||||||
|
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview2"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="false"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:clip-to-page="false"
|
||||||
|
inkscape:antialias-rendering="true"
|
||||||
|
showborder="true"
|
||||||
|
inkscape:zoom="3.608628"
|
||||||
|
inkscape:cx="47.38643"
|
||||||
|
inkscape:cy="80.501509"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1014"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<!-- Filled triangle -->
|
||||||
|
<path
|
||||||
|
stroke-width="15.0001"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke="#ff8888"
|
||||||
|
fill="#ff8888"
|
||||||
|
d="M 157.49952,389.99997 H 307.50049 L 232.5,254.99926 Z"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
stroke-width="15"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke="#000000"
|
||||||
|
fill="none"
|
||||||
|
d="m 232.49948,283.49921 v 60"
|
||||||
|
id="path2" />
|
||||||
|
<circle
|
||||||
|
cx="232.56358"
|
||||||
|
cy="371.59991"
|
||||||
|
stroke="rgb(255,136,136)"
|
||||||
|
stroke-width="7.50004"
|
||||||
|
fill="rgb(0,0,0)"
|
||||||
|
id="circle2"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
r="7.5" />
|
||||||
|
<!-- Vertical line -->
|
||||||
|
<!-- Circle below the vertical line -->
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
9
public/mock/notice.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
This is the mock folder, put your `icons` folder and `apps.json` in the folder where this file is located
|
||||||
|
|
||||||
|
How to obtain? (you may ask)
|
||||||
|
1. Open Bridge Launcher
|
||||||
|
2. Open it's Settings
|
||||||
|
3. Scroll down to 'development'
|
||||||
|
4. Click 'Export'
|
||||||
|
5. Select any path
|
||||||
|
6. Transfer the needed files (`icons` folder and `apps.json`) here
|
1
public/vite.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
public/vue.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
57
src/App.vue
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<template>
|
||||||
|
<div id="applist-wrapper">
|
||||||
|
<Suspense>
|
||||||
|
<template #default>
|
||||||
|
<div id="applist">
|
||||||
|
<div class="applist-page" v-for="(page, index) in paginatedApps" :key="index">
|
||||||
|
<AppIcon v-for="app in page" :key="app.packageName" :packageName="app.packageName" :label="app.label" />
|
||||||
|
</div>
|
||||||
|
<div class="applist-page" v-if="paginatedApps.length > 0">
|
||||||
|
<Settings></Settings>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
<div id="dock-wrapper">
|
||||||
|
<Search></Search>
|
||||||
|
<Dock></Dock>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onUnmounted } from 'vue';
|
||||||
|
import AppIcon from './components/AppIcon.vue';
|
||||||
|
import Dock from './components/Dock.vue';
|
||||||
|
import Search from './components/Search.vue';
|
||||||
|
import Settings from './components/Settings.vue';
|
||||||
|
|
||||||
|
const apps = ref([])
|
||||||
|
|
||||||
|
async function loadApps() {
|
||||||
|
const resp = await fetch(Bridge.getAppsURL())
|
||||||
|
const data = await resp.json()
|
||||||
|
apps.value = data.apps.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))
|
||||||
|
window.springboard.apps = apps.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadApps()
|
||||||
|
|
||||||
|
let lastUpdate = 0;
|
||||||
|
function updateApps() {
|
||||||
|
const now = performance.now();
|
||||||
|
if (now - lastUpdate >= 100) {
|
||||||
|
loadApps();
|
||||||
|
lastUpdate = now;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(updateApps);
|
||||||
|
}
|
||||||
|
updateApps();
|
||||||
|
|
||||||
|
const itemsPerPage = 4 * 6;
|
||||||
|
const paginatedApps = computed(() => {
|
||||||
|
return Array(Math.ceil(apps.value.length / itemsPerPage)).fill().map((_, index) => {
|
||||||
|
return apps.value.slice(index * itemsPerPage, (index + 1) * itemsPerPage);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
99
src/components/AppIcon.vue
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<script setup>
|
||||||
|
import { defineProps, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
packageName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "UNSETLABEL"
|
||||||
|
},
|
||||||
|
hideLabel: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let darkLabel
|
||||||
|
if (Bridge.getSystemNightMode() == 'yes') {
|
||||||
|
darkLabel = true;
|
||||||
|
} else {
|
||||||
|
darkLabel = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = ref('');
|
||||||
|
icon.value = await Bridge.getDefaultAppIconURL(props.packageName);
|
||||||
|
const label = ref('')
|
||||||
|
if (props.label.length >= 12) {
|
||||||
|
label.value = `${props.label.slice(0, 12 - 3).trim()}...`;
|
||||||
|
} else {
|
||||||
|
label.value = props.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
Bridge.requestLaunchApp(props.packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadIcon() {
|
||||||
|
const iconsrc = await Bridge.getDefaultAppIconURL(props.packageName);
|
||||||
|
const img = new Image();
|
||||||
|
img.src = iconsrc;
|
||||||
|
img.onload = () => {
|
||||||
|
icon.value = iconsrc;
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
if (icon.value != '/error.svg') icon.value = '/error.svg';
|
||||||
|
setTimeout(loadIcon, 100);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
loadIcon();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="app-icon"
|
||||||
|
@click="handleClick()"
|
||||||
|
:style="{ '--label-color': darkLabel ? '#000000' : '#ffffff' }"
|
||||||
|
>
|
||||||
|
<img :src="icon" :alt="label" class="app-image">
|
||||||
|
<span v-if="!props.hideLabel" class="app-label">{{ label }}</span>
|
||||||
|
<span v-if="!props.hideLabel" class="app-package">{{ props.packageName }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: max-content;
|
||||||
|
height: max-content;
|
||||||
|
|
||||||
|
.app-icon > *:nth-child(n+2) {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-image {
|
||||||
|
width: 14vw;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 10pt;
|
||||||
|
color: var(--label-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-package {
|
||||||
|
font-size: 1px;
|
||||||
|
color: #00000000;
|
||||||
|
}
|
||||||
|
</style>
|
14
src/components/Dock.vue
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup>
|
||||||
|
import AppIcon from './AppIcon.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Suspense>
|
||||||
|
<div id="dock">
|
||||||
|
<AppIcon packageName="app.revenge" :hideLabel="true"/>
|
||||||
|
<AppIcon packageName="com.radolyn.ayugram" :hideLabel="true"/>
|
||||||
|
<AppIcon packageName="org.mozilla.fennec_fdroid" :hideLabel="true"/>
|
||||||
|
<AppIcon packageName="org.akanework.gramophone" :hideLabel="true"/>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
</template>
|
152
src/components/Search.vue
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
<template>
|
||||||
|
<Suspense>
|
||||||
|
<div id="search_background">
|
||||||
|
<div class="section" id="search_results">
|
||||||
|
<AppIcon
|
||||||
|
v-for="(app, index) in filteredApps"
|
||||||
|
:key="app.packageName || index"
|
||||||
|
:packageName="app.packageName"
|
||||||
|
:label="app.label"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
<div id="search_maindiv">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
@focus="openSearch"
|
||||||
|
placeholder="Search"
|
||||||
|
>
|
||||||
|
<i class="bi bi-x-circle"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import fuzzy from 'fuzzy';
|
||||||
|
import AppIcon from './AppIcon.vue';
|
||||||
|
|
||||||
|
const filteredApps = ref([]);
|
||||||
|
|
||||||
|
function openSearch() {
|
||||||
|
const inputElement = document.querySelector('#search_maindiv input');
|
||||||
|
const backgroundElement = document.querySelector('#search_background');
|
||||||
|
|
||||||
|
function handleInput() {
|
||||||
|
const value = inputElement.value.toString();
|
||||||
|
|
||||||
|
const apps = window.springboard.apps || [];
|
||||||
|
const searchList = apps.map(app => app.label);
|
||||||
|
|
||||||
|
const results = fuzzy.filter(value, searchList);
|
||||||
|
|
||||||
|
filteredApps.value = results
|
||||||
|
.slice(0, 8)
|
||||||
|
.map(result => apps[result.index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputElement.hasAttribute('data-added')) {
|
||||||
|
inputElement.addEventListener('input', handleInput);
|
||||||
|
inputElement.addEventListener('blur', async () => {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
inputElement.value = '';
|
||||||
|
backgroundElement.style.opacity = '0';
|
||||||
|
backgroundElement.style.pointerEvents = 'none';
|
||||||
|
inputElement.removeEventListener('input', handleInput);
|
||||||
|
filteredApps.value = [];
|
||||||
|
inputElement.removeAttribute('data-added');
|
||||||
|
});
|
||||||
|
inputElement.setAttribute('data-added', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundElement.style.opacity = '1';
|
||||||
|
backgroundElement.style.pointerEvents = 'all';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'bootstrap-icons/font/bootstrap-icons.css';
|
||||||
|
|
||||||
|
#search_results {
|
||||||
|
scroll-snap-align: center;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 25%);
|
||||||
|
grid-template-rows: repeat(2, 50%);
|
||||||
|
place-items: center;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search_background {
|
||||||
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 99;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
padding: 5%;
|
||||||
|
transition: opacity 0.25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search_maindiv {
|
||||||
|
z-index: 999;
|
||||||
|
width: 25% !important;
|
||||||
|
border: none;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 300px;
|
||||||
|
padding: 2% 4%;
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 3%;
|
||||||
|
display: flex;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
&:has(input:focus) {
|
||||||
|
transition: all 0.25s ease-in-out;
|
||||||
|
transform:translateY(-550%);
|
||||||
|
width: 80% !important;
|
||||||
|
& > i {
|
||||||
|
transition: all 0.5s ease-in-out;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: all;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
& > input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:has(input:not(:placeholder-shown)) {
|
||||||
|
& > i {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: white;
|
||||||
|
width: 100% !important;
|
||||||
|
text-align: center;
|
||||||
|
&::placeholder {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
&::placeholder {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
position: relative;
|
||||||
|
right: 0;
|
||||||
|
opacity: 0.0;
|
||||||
|
transition: all 0.25s ease-in-out;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 0;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
</style>
|
72
src/components/Settings.vue
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
function toggleBridgeButton() {
|
||||||
|
console.log('Changed Bridge Button visibility to...')
|
||||||
|
if (Bridge.getBridgeButtonVisibility() == 'shown') {
|
||||||
|
Bridge.requestSetBridgeButtonVisibility('hidden');
|
||||||
|
console.log('hidden');
|
||||||
|
} else {
|
||||||
|
Bridge.requestSetBridgeButtonVisibility('shown');
|
||||||
|
console.log('shown');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSystemWallpapers() {
|
||||||
|
console.log('Changed draw system wallpapers to...')
|
||||||
|
if (Bridge.getDrawSystemWallpaperBehindWebViewEnabled()) {
|
||||||
|
Bridge.requestSetDrawSystemWallpaperBehindWebViewEnabled(false);
|
||||||
|
document.body.style.backgroundColor = '';
|
||||||
|
console.log('false');
|
||||||
|
} else {
|
||||||
|
Bridge.requestSetDrawSystemWallpaperBehindWebViewEnabled(true);
|
||||||
|
console.log('true');
|
||||||
|
document.body.style.backgroundColor = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOverscrolling() {
|
||||||
|
console.log('Changed overscrolling effect to...')
|
||||||
|
if (Bridge.getOverscrollEffects == 'default') {
|
||||||
|
Bridge.requestSetOverscrollEffects('none');
|
||||||
|
console.log('none');
|
||||||
|
} else {
|
||||||
|
Bridge.requestSetOverscrollEffects('default');
|
||||||
|
console.log('default');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBridgeAppDrawer() {
|
||||||
|
Bridge.requestOpenBridgeAppDrawer();
|
||||||
|
}
|
||||||
|
function reloadWindow() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="section">
|
||||||
|
<button class="ui-button" @click="toggleBridgeButton()">Toggle Bridge button</button>
|
||||||
|
<button class="ui-button" @click="openBridgeAppDrawer()">Open Bridge App Drawer</button>
|
||||||
|
<button class="ui-button" @click="toggleSystemWallpapers()">Toggle system wallpaper visibility</button>
|
||||||
|
<button class="ui-button" @click="toggleOverscrolling()">Toggle overscrolling</button>
|
||||||
|
<button class="ui-button" @click="reloadWindow()">Reload</button>
|
||||||
|
<br>
|
||||||
|
<small style="font-size:xx-small;">Everything else can be configured through Bridge's Settings</small>
|
||||||
|
</div>
|
||||||
|
<div class="section" style="display:flex;flex-direction:column;align-items: center;">
|
||||||
|
<span style="width:max-content;">Made with</span>
|
||||||
|
<div style="display:flex;justify-content:space-around;width:100%;">
|
||||||
|
<img src="/vite.svg">
|
||||||
|
<img src="/vue.svg">
|
||||||
|
<img src="/com.tored.bridgelauncher.png" alt="Bridge">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
img {
|
||||||
|
width: 15%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
20
src/main.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import './style.scss'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { BridgeMock } from '@bridgelauncher/api-mock';
|
||||||
|
|
||||||
|
if (!window.Bridge) {
|
||||||
|
window.Bridge = new BridgeMock({
|
||||||
|
appsUrl: '/mock/apps.json',
|
||||||
|
makeGetDefaultIconUrl: (packageName) => `/mock/icons/default/${packageName}.png`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Bridge.requestSetBridgeTheme('system');
|
||||||
|
|
||||||
|
window.springboard = {
|
||||||
|
apps: [],
|
||||||
|
labelColor: 'ffffff'
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
90
src/style.scss
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#applist {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scroll-snap-stop: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
#applist-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.applist-page {
|
||||||
|
scroll-snap-align: center;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 25%);
|
||||||
|
grid-template-rows: repeat(5, 20%);
|
||||||
|
place-items: center;
|
||||||
|
align-items: start;
|
||||||
|
min-width: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
|
width: 100vw;
|
||||||
|
padding-bottom: 25%;
|
||||||
|
padding-top: 5%;
|
||||||
|
& > *:not(.section) {
|
||||||
|
width: auto;
|
||||||
|
margin-bottom: 5%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&:has(.section) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5%;
|
||||||
|
gap: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#dock-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dock {
|
||||||
|
padding: 5%;
|
||||||
|
gap: 15px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
border: 1px solid white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 5%;
|
||||||
|
border-radius: 15px;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
border: 1px solid white;
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-button {
|
||||||
|
border: none;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
color: white;
|
||||||
|
margin: 1%;
|
||||||
|
}
|
19
vite.config.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import sass from 'sass';
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
sass: {
|
||||||
|
implementation: sass,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
})
|