archdroid-parallel-workers / components /arch-app-drawer.js
jonin925's picture
lets deploy parallel workers to speed up the development process. make the app fetch current settings, app data from android to display in the application drawer. the navigation gestures and other daily use settings like the ones in notification bar should comply with current android settings. help me build an apk ready pwa stack that can be tested on samsung s23 ultra
3a08226 verified
Raw
History Blame Contribute Delete
9.71 kB
class ArchAppDrawer extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.apps = [];
}
connectedCallback() {
this.render();
this.loadApps();
this.setupSearch();
this.setupCloseGesture();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: flex;
flex-direction: column;
background: #0f172a;
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
font-family: 'JetBrains Mono', monospace;
}
.header {
padding: calc(env(safe-area-inset-top, 24px) + 16px) 24px 16px;
background: linear-gradient(to bottom, #020617, transparent);
border-bottom: 1px solid #334155;
}
.search-container {
display: flex;
align-items: center;
gap: 12px;
background: rgba(51, 65, 85, 0.5);
border: 1px solid #475569;
border-radius: 8px;
padding: 12px 16px;
margin-bottom: 8px;
}
.search-icon {
color: #06b6d4;
width: 18px;
height: 18px;
}
input {
background: transparent;
border: none;
color: #e2e8f0;
font-family: inherit;
font-size: 14px;
outline: none;
width: 100%;
caret-color: #06b6d4;
}
input::placeholder {
color: #64748b;
}
.hint {
font-size: 11px;
color: #64748b;
margin-left: 8px;
}
.app-list {
flex: 1;
overflow-y: auto;
padding: 8px 16px calc(env(safe-area-inset-bottom, 16px) + 80px);
}
.category {
margin: 16px 0 8px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 2px;
color: #06b6d4;
border-left: 2px solid #06b6d4;
padding-left: 8px;
}
.app-item {
display: flex;
align-items: center;
gap: 16px;
padding: 12px;
margin: 4px 0;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
}
.app-item:hover, .app-item:active {
background: rgba(51, 65, 85, 0.5);
border-color: rgba(6, 182, 212, 0.3);
}
.app-icon-small {
width: 40px;
height: 40px;
background: rgba(51, 65, 85, 0.6);
border: 1px solid rgba(100, 116, 139, 0.4);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: #e2e8f0;
}
.app-info {
flex: 1;
}
.app-name {
font-size: 14px;
color: #e2e8f0;
margin-bottom: 2px;
}
.app-package {
font-size: 11px;
color: #64748b;
font-family: monospace;
}
.handle {
position: absolute;
top: env(safe-area-inset-top, 12px);
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 4px;
background: #475569;
border-radius: 2px;
cursor: pointer;
}
.close-hint {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
color: #64748b;
font-size: 11px;
display: flex;
align-items: center;
gap: 6px;
opacity: 0.7;
}
.arrow {
animation: bounce 1s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
.no-results {
text-align: center;
color: #64748b;
padding: 48px 24px;
font-size: 13px;
}
</style>
<div class="handle"></div>
<div class="header">
<div class="search-container">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input type="text" placeholder="search packages..." id="search-input">
</div>
<div class="hint">$ pacman -Ss [query]</div>
</div>
<div class="app-list" id="app-list"></div>
<div class="close-hint">
<span>swipe down to close</span>
<span class="arrow">↓</span>
</div>
`;
}
loadApps() {
// Get apps from main script
if (window.launcher) {
this.apps = window.launcher.getAllApps();
this.renderAppList(this.apps);
} else {
// Fallback
setTimeout(() => this.loadApps(), 100);
}
}
renderAppList(apps) {
const list = this.shadowRoot.getElementById('app-list');
if (!list) return;
// Group by category
const grouped = apps.reduce((acc, app) => {
if (!acc[app.category]) acc[app.category] = [];
acc[app.category].push(app);
return acc;
}, {});
list.innerHTML = '';
Object.entries(grouped).forEach(([category, categoryApps]) => {
const catEl = document.createElement('div');
catEl.className = 'category';
catEl.textContent = category;
list.appendChild(catEl);
categoryApps.forEach(app => {
const item = document.createElement('div');
item.className = 'app-item';
item.innerHTML = `
<div class="app-icon-small">
<i data-feather="${app.icon}" style="width: 20px; height: 20px;"></i>
</div>
<div class="app-info">
<div class="app-name">${app.name}</div>
<div class="app-package">:${app.category}/${app.name.toLowerCase()}</div>
</div>
`;
item.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('launch-app', {
bubbles: true,
composed: true,
detail: app
}));
});
list.appendChild(item);
});
});
if (window.feather) {
feather.replace({ parent: this.shadowRoot });
}
}
setupSearch() {
const input = this.shadowRoot.getElementById('search-input');
if (!input) return;
input.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
if (!query) {
this.renderAppList(this.apps);
return;
}
const filtered = this.apps.filter(app =>
app.name.toLowerCase().includes(query) ||
app.category.toLowerCase().includes(query)
);
const list = this.shadowRoot.getElementById('app-list');
if (filtered.length === 0) {
list.innerHTML = '<div class="no-results">no packages found matching "' + query + '"</div>';
} else {
this.renderAppList(filtered);
}
});
}
setupCloseGesture() {
let startY = 0;
const drawer = this;
drawer.addEventListener('touchstart', (e) => {
startY = e.touches[0].clientY;
}, { passive: true });
drawer.addEventListener('touchmove', (e) => {
const currentY = e.touches[0].clientY;
const diff = currentY - startY;
if (diff > 0 && drawer.scrollTop === 0) {
// User is pulling down at top of scroll
}
}, { passive: true });
}
}
customElements.define('arch-app-drawer', ArchAppDrawer);