Compare commits

..

6 commits

30 changed files with 2899 additions and 12 deletions

4
.gitignore vendored
View file

@ -90,7 +90,6 @@ out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
@ -130,3 +129,6 @@ dist
.yarn/install-state.gz
.pnp.*
# Mock hiding
public/mock/*
!public/mock/notice.txt

3
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

1105
Bridge-mock-APIs.d.ts vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,13 @@
# BLHS
Bridge Launcher Home Screens I made for myself but published for no reason
# SpringBoard-like
# How to install
1. **Go to Branches** -> (select any EXCEPT `template`)
2. **Go to Release**
3. **Download** the source code `.zip` file
4. **Unarchive** the downloaded `.zip` file into any directory
> Note: You only need `dist` folder from there, you can safely delete everything else
5. **Open Bridge Launcher** and go to settings to **choose the `dist` folder from unarchived the `.zip` file as the Project Folder**
A Brige Launcher Project inspired by SpringBoard, iOS default launcher,
# License
[MIT](LICENSE)
# 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

Binary file not shown.

Binary file not shown.

5
dist/assets/index-_sk3xYAV.css vendored Normal file

File diff suppressed because one or more lines are too long

17
dist/assets/index-mum_jNSN.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/bundle.zip vendored Normal file

Binary file not shown.

BIN
dist/com.tored.bridgelauncher.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

65
dist/error.svg vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

65
public/error.svg Normal file
View 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
View 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
View 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
View 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
View 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>

View 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
View 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
View 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>

View 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
View 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
View 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
View 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,
},
})