feat: カスタムアプリ登録機能追加 — アイコンピッカー・イニシャルバッジ・削除ボタン
This commit is contained in:
parent
22bb95a10a
commit
4304bcdc96
|
|
@ -0,0 +1 @@
|
||||||
|
.vercel
|
||||||
525
index.html
525
index.html
|
|
@ -158,13 +158,20 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 4px 16px 0;
|
padding: 8px 16px 0;
|
||||||
min-height: 32px;
|
min-height: 36px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.edit-bar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.edit-bar-hint { font-size: 11px; color: var(--text3); }
|
.edit-bar-hint { font-size: 11px; color: var(--text3); }
|
||||||
.edit-toggle-btn {
|
.edit-toggle-btn {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 4px 12px;
|
padding: 5px 12px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
@ -172,6 +179,7 @@
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.12s;
|
transition: all 0.12s;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.edit-toggle-btn.active {
|
.edit-toggle-btn.active {
|
||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
|
|
@ -179,6 +187,25 @@
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Add app button ── */
|
||||||
|
.add-app-btn {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px 11px 5px 8px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px dashed var(--accent);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.12s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.add-app-btn.visible { display: flex; }
|
||||||
|
.add-app-btn:active { background: var(--surface); }
|
||||||
|
|
||||||
/* ── App sections ── */
|
/* ── App sections ── */
|
||||||
.app-section { padding: 0 16px 16px; }
|
.app-section { padding: 0 16px 16px; }
|
||||||
.section-label {
|
.section-label {
|
||||||
|
|
@ -237,6 +264,19 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Text initial badge ── */
|
||||||
|
.app-initial {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.check-badge {
|
.check-badge {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -251,6 +291,29 @@
|
||||||
}
|
}
|
||||||
.app-item.edit-mode.on .check-badge { display: flex; }
|
.app-item.edit-mode.on .check-badge { display: flex; }
|
||||||
|
|
||||||
|
/* ── Custom app delete button ── */
|
||||||
|
.custom-del-btn {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 4px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: #EF4444;
|
||||||
|
border-radius: 50%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.app-item.edit-mode .custom-del-btn { display: flex; }
|
||||||
|
|
||||||
/* ── Empty state ── */
|
/* ── Empty state ── */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -353,6 +416,176 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Add app modal ── */
|
||||||
|
.add-modal-backdrop {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,.55);
|
||||||
|
z-index: 200;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
.add-modal-backdrop.open { display: block; }
|
||||||
|
.add-modal {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--surface);
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
padding-bottom: max(24px, env(safe-area-inset-bottom));
|
||||||
|
z-index: 201;
|
||||||
|
max-height: 88vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.25s cubic-bezier(0.2,0.9,0.2,1);
|
||||||
|
}
|
||||||
|
.add-modal.open { transform: translateY(0); }
|
||||||
|
.add-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 16px 12px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--surface);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.add-modal-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.add-modal-body {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.form-field-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text2);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.form-text-input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.form-text-input:focus { border-color: var(--accent); }
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239CA3AF' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 12px center;
|
||||||
|
padding-right: 32px;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.form-select:focus { border-color: var(--accent); }
|
||||||
|
|
||||||
|
.icon-picker-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text2);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.icon-picker-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.icon-pick-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px 4px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.12s;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
.icon-pick-btn.selected {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.icon-pick-btn:active { background: var(--surface2); }
|
||||||
|
.icon-initial-pick {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 9px 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text2);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.12s;
|
||||||
|
margin-top: 6px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.icon-initial-pick.selected {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.initial-preview {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: var(--surface2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.form-submit-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #0D0D0D;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.form-submit-btn:disabled {
|
||||||
|
opacity: 0.38;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Safe area ── */
|
/* ── Safe area ── */
|
||||||
main { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
|
main { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -432,6 +665,61 @@
|
||||||
</aside>
|
</aside>
|
||||||
<div class="overlay" id="overlay" aria-hidden="true"></div>
|
<div class="overlay" id="overlay" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<!-- Add app modal -->
|
||||||
|
<div class="add-modal-backdrop" id="addModalBackdrop"></div>
|
||||||
|
<div class="add-modal" id="addModal" role="dialog" aria-modal="true" aria-label="アプリを追加">
|
||||||
|
<div class="add-modal-header">
|
||||||
|
<span class="add-modal-title">アプリを追加</span>
|
||||||
|
<button class="icon-btn" id="addModalClose" aria-label="閉じる">
|
||||||
|
<i data-lucide="x" style="width:18px;height:18px;stroke-width:1.75"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="add-modal-body">
|
||||||
|
<div>
|
||||||
|
<div class="form-field-label">アプリ名 *</div>
|
||||||
|
<input class="form-text-input" id="addName" type="text"
|
||||||
|
placeholder="例: Notion" autocomplete="off" maxlength="30">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="form-field-label">カテゴリ *</div>
|
||||||
|
<select class="form-select" id="addCat">
|
||||||
|
<option value="">選択してください</option>
|
||||||
|
<option value="sns">SNS・メッセージ</option>
|
||||||
|
<option value="media">動画・音楽</option>
|
||||||
|
<option value="news">ニュース</option>
|
||||||
|
<option value="shop">ショッピング・決済</option>
|
||||||
|
<option value="nav">マップ・移動</option>
|
||||||
|
<option value="tools">ツール</option>
|
||||||
|
<option value="posimai">Posimai</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="form-field-label">アイコン *</div>
|
||||||
|
<div class="icon-picker-grid" id="iconPickerGrid"></div>
|
||||||
|
<button class="icon-initial-pick" id="iconInitialPick" type="button">
|
||||||
|
<span class="initial-preview" id="initialPreviewChar">A</span>
|
||||||
|
イニシャルバッジ(名前の頭文字を使う)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="form-field-label">Android パッケージ名</div>
|
||||||
|
<input class="form-text-input" id="addAndroid" type="text"
|
||||||
|
placeholder="例: com.notion.id" autocomplete="off" autocapitalize="none" autocorrect="off">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="form-field-label">iOS URL スキーム</div>
|
||||||
|
<input class="form-text-input" id="addIos" type="text"
|
||||||
|
placeholder="例: notion://" autocomplete="off" autocapitalize="none" autocorrect="off">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="form-field-label">ウェブ URL</div>
|
||||||
|
<input class="form-text-input" id="addWeb" type="url"
|
||||||
|
placeholder="例: https://notion.so" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<button class="form-submit-btn" id="addSubmitBtn" disabled>追加する</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
|
|
||||||
<!-- 時計・天気・挨拶ゾーン -->
|
<!-- 時計・天気・挨拶ゾーン -->
|
||||||
|
|
@ -465,7 +753,13 @@
|
||||||
<div class="cat-scroll" id="catScroll" role="tablist" aria-label="カテゴリフィルタ"></div>
|
<div class="cat-scroll" id="catScroll" role="tablist" aria-label="カテゴリフィルタ"></div>
|
||||||
|
|
||||||
<div class="edit-bar">
|
<div class="edit-bar">
|
||||||
|
<div class="edit-bar-left">
|
||||||
|
<button class="add-app-btn" id="addAppBtn" aria-label="アプリを追加">
|
||||||
|
<i data-lucide="plus" style="width:13px;height:13px;stroke-width:2.5"></i>
|
||||||
|
アプリを追加
|
||||||
|
</button>
|
||||||
<span class="edit-bar-hint" id="editBarHint"></span>
|
<span class="edit-bar-hint" id="editBarHint"></span>
|
||||||
|
</div>
|
||||||
<button class="edit-toggle-btn" id="editToggleBtn">編集</button>
|
<button class="edit-toggle-btn" id="editToggleBtn">編集</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -482,6 +776,7 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
const ENABLED_KEY = 'posimai-veil-enabled';
|
const ENABLED_KEY = 'posimai-veil-enabled';
|
||||||
|
const CUSTOM_KEY = 'posimai-veil-custom';
|
||||||
const API_KEY_KEY = 'posimai_api_key';
|
const API_KEY_KEY = 'posimai_api_key';
|
||||||
const COLOR_MODE_KEY = 'posimai-veil-color-mode';
|
const COLOR_MODE_KEY = 'posimai-veil-color-mode';
|
||||||
const API_BASE = 'https://posimai-lab.tail72e846.ts.net/brain/api';
|
const API_BASE = 'https://posimai-lab.tail72e846.ts.net/brain/api';
|
||||||
|
|
@ -489,29 +784,41 @@ const API_BASE = 'https://posimai-lab.tail72e846.ts.net/brain/api';
|
||||||
// ── カテゴリカラー(ダーク / ライト) ──────────────────────
|
// ── カテゴリカラー(ダーク / ライト) ──────────────────────
|
||||||
const CAT_COLORS = {
|
const CAT_COLORS = {
|
||||||
dark: {
|
dark: {
|
||||||
posimai: '#6EE7B7', // Teal(ブランドカラー)
|
posimai: '#6EE7B7',
|
||||||
sns: '#60A5FA', // Blue
|
sns: '#60A5FA',
|
||||||
media: '#F472B6', // Pink
|
media: '#F472B6',
|
||||||
news: '#FB923C', // Orange
|
news: '#FB923C',
|
||||||
tools: '#94A3B8', // Slate
|
tools: '#94A3B8',
|
||||||
nav: '#4ADE80', // Green
|
nav: '#4ADE80',
|
||||||
shop: '#FBBF24', // Amber
|
shop: '#FBBF24',
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
posimai: '#059669', // Emerald-600
|
posimai: '#059669',
|
||||||
sns: '#2563EB', // Blue-600
|
sns: '#2563EB',
|
||||||
media: '#DB2777', // Pink-600
|
media: '#DB2777',
|
||||||
news: '#EA580C', // Orange-600
|
news: '#EA580C',
|
||||||
tools: '#475569', // Slate-600
|
tools: '#475569',
|
||||||
nav: '#16A34A', // Green-600
|
nav: '#16A34A',
|
||||||
shop: '#D97706', // Amber-600
|
shop: '#D97706',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── アイコンピッカー候補リスト ──────────────────────────────
|
||||||
|
const ICON_PICKER_ICONS = [
|
||||||
|
'message-circle','message-square','send','at-sign','phone','mail','users',
|
||||||
|
'play','music','film','tv','radio','headphones','mic','airplay',
|
||||||
|
'camera','image','aperture','eye','star','heart','bookmark',
|
||||||
|
'map-pin','map','navigation','compass','globe',
|
||||||
|
'shopping-cart','shopping-bag','tag','credit-card','wallet',
|
||||||
|
'newspaper','rss','trending-up','bar-chart-2',
|
||||||
|
'calendar','clock','bell','search','share',
|
||||||
|
'settings','lock','key','shield','zap',
|
||||||
|
'briefcase','book','coffee','home','layers',
|
||||||
|
'monitor','smartphone','cpu','hard-drive','file-text',
|
||||||
|
'calculator','activity','layout-dashboard','check-circle',
|
||||||
|
];
|
||||||
|
|
||||||
// ── アプリDB ────────────────────────────────────────────────
|
// ── アプリDB ────────────────────────────────────────────────
|
||||||
// android : Androidパッケージ名(_camera/_phone/_settings は特殊intent)
|
|
||||||
// ios : iOS URLスキーム
|
|
||||||
// web : HTTPSフォールバック
|
|
||||||
const APP_DB = [
|
const APP_DB = [
|
||||||
// SNS・メッセージ
|
// SNS・メッセージ
|
||||||
{ id:'line', label:'LINE', icon:'message-circle', cat:'sns',
|
{ id:'line', label:'LINE', icon:'message-circle', cat:'sns',
|
||||||
|
|
@ -647,12 +954,27 @@ const CAT_LABELS = {
|
||||||
|
|
||||||
const CAT_ORDER = ['posimai','sns','media','news','tools','nav','shop'];
|
const CAT_ORDER = ['posimai','sns','media','news','tools','nav','shop'];
|
||||||
|
|
||||||
|
// ── カスタムアプリ ──────────────────────────────────────────
|
||||||
|
function loadCustomApps() {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(CUSTOM_KEY);
|
||||||
|
if (raw) return JSON.parse(raw);
|
||||||
|
} catch {}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCustomApps() {
|
||||||
|
localStorage.setItem(CUSTOM_KEY, JSON.stringify(customApps));
|
||||||
|
}
|
||||||
|
|
||||||
|
let customApps = loadCustomApps();
|
||||||
|
|
||||||
// ── 状態 ────────────────────────────────────────────────────
|
// ── 状態 ────────────────────────────────────────────────────
|
||||||
let enabledIds = loadEnabled();
|
let enabledIds = loadEnabled();
|
||||||
let activeCat = 'all';
|
let activeCat = 'all';
|
||||||
let searchQ = '';
|
let searchQ = '';
|
||||||
let editMode = false;
|
let editMode = false;
|
||||||
let colorMode = localStorage.getItem(COLOR_MODE_KEY) || 'accent'; // 'accent' | 'colorful'
|
let colorMode = localStorage.getItem(COLOR_MODE_KEY) || 'accent';
|
||||||
let apiKey = localStorage.getItem(API_KEY_KEY) || '';
|
let apiKey = localStorage.getItem(API_KEY_KEY) || '';
|
||||||
|
|
||||||
function getTheme() {
|
function getTheme() {
|
||||||
|
|
@ -669,13 +991,20 @@ function loadEnabled() {
|
||||||
const raw = localStorage.getItem(ENABLED_KEY);
|
const raw = localStorage.getItem(ENABLED_KEY);
|
||||||
if (raw) return new Set(JSON.parse(raw));
|
if (raw) return new Set(JSON.parse(raw));
|
||||||
} catch {}
|
} catch {}
|
||||||
return new Set(APP_DB.map(a => a.id)); // デフォルト: 全ON
|
// デフォルト: 全 ON(組み込み + カスタム)
|
||||||
|
const base = new Set(APP_DB.map(a => a.id));
|
||||||
|
customApps.forEach(a => base.add(a.id));
|
||||||
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveEnabled() {
|
function saveEnabled() {
|
||||||
localStorage.setItem(ENABLED_KEY, JSON.stringify([...enabledIds]));
|
localStorage.setItem(ENABLED_KEY, JSON.stringify([...enabledIds]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function allApps() {
|
||||||
|
return [...APP_DB, ...customApps];
|
||||||
|
}
|
||||||
|
|
||||||
// ── アプリ起動 ──────────────────────────────────────────────
|
// ── アプリ起動 ──────────────────────────────────────────────
|
||||||
function launchApp(app) {
|
function launchApp(app) {
|
||||||
const ua = navigator.userAgent.toLowerCase();
|
const ua = navigator.userAgent.toLowerCase();
|
||||||
|
|
@ -714,6 +1043,16 @@ function toggleApp(id) {
|
||||||
renderApps();
|
renderApps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── カスタムアプリ削除 ──────────────────────────────────────
|
||||||
|
function deleteCustomApp(id) {
|
||||||
|
customApps = customApps.filter(a => a.id !== id);
|
||||||
|
saveCustomApps();
|
||||||
|
enabledIds.delete(id);
|
||||||
|
saveEnabled();
|
||||||
|
renderApps();
|
||||||
|
showToast('削除しました');
|
||||||
|
}
|
||||||
|
|
||||||
// ── カテゴリタブ描画 ────────────────────────────────────────
|
// ── カテゴリタブ描画 ────────────────────────────────────────
|
||||||
function renderCatTabs() {
|
function renderCatTabs() {
|
||||||
const wrap = document.getElementById('catScroll');
|
const wrap = document.getElementById('catScroll');
|
||||||
|
|
@ -737,12 +1076,15 @@ function renderApps() {
|
||||||
const container = document.getElementById('appSections');
|
const container = document.getElementById('appSections');
|
||||||
const hint = document.getElementById('editBarHint');
|
const hint = document.getElementById('editBarHint');
|
||||||
const editBtn = document.getElementById('editToggleBtn');
|
const editBtn = document.getElementById('editToggleBtn');
|
||||||
|
const addBtn = document.getElementById('addAppBtn');
|
||||||
|
|
||||||
editBtn.classList.toggle('active', editMode);
|
editBtn.classList.toggle('active', editMode);
|
||||||
hint.textContent = editMode ? 'タップで表示/非表示を切り替え' : '';
|
hint.textContent = editMode ? 'タップで表示/非表示を切り替え' : '';
|
||||||
|
addBtn.classList.toggle('visible', editMode);
|
||||||
|
|
||||||
const q = searchQ.toLowerCase();
|
const q = searchQ.toLowerCase();
|
||||||
const filtered = APP_DB.filter(app => {
|
const source = allApps();
|
||||||
|
const filtered = source.filter(app => {
|
||||||
if (activeCat !== 'all' && app.cat !== activeCat) return false;
|
if (activeCat !== 'all' && app.cat !== activeCat) return false;
|
||||||
if (q && !app.label.toLowerCase().includes(q) && !app.id.includes(q)) return false;
|
if (q && !app.label.toLowerCase().includes(q) && !app.id.includes(q)) return false;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -772,11 +1114,6 @@ function renderApps() {
|
||||||
const color = getCatColor(cat);
|
const color = getCatColor(cat);
|
||||||
const isColorful = colorMode === 'colorful';
|
const isColorful = colorMode === 'colorful';
|
||||||
|
|
||||||
// カラフルモード時: カードに極薄の色背景+ボーダー
|
|
||||||
const cardColorStyle = isColorful
|
|
||||||
? `--item-color:${color};`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="app-section">
|
<div class="app-section">
|
||||||
<div class="section-label"
|
<div class="section-label"
|
||||||
|
|
@ -789,13 +1126,24 @@ function renderApps() {
|
||||||
const bgStyle = isColorful
|
const bgStyle = isColorful
|
||||||
? `background:${color}14;border-color:${color}38;`
|
? `background:${color}14;border-color:${color}38;`
|
||||||
: '';
|
: '';
|
||||||
|
const isCustom = !!app.custom;
|
||||||
|
const isInitial = app.icon === '_initial';
|
||||||
|
|
||||||
|
const iconHTML = isInitial
|
||||||
|
? `<div class="app-initial" style="background:${color}22;color:${color}">${app.label.charAt(0).toUpperCase()}</div>`
|
||||||
|
: `<i data-lucide="${app.icon}" class="app-icon" style="stroke:${color}"></i>`;
|
||||||
|
|
||||||
|
const delBtn = isCustom
|
||||||
|
? `<button class="custom-del-btn" data-del-id="${app.id}" aria-label="${app.label}を削除">✕</button>`
|
||||||
|
: '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="app-item${editCls}${hidden}"
|
<div class="app-item${editCls}${hidden}"
|
||||||
data-id="${app.id}" role="button" tabindex="0"
|
data-id="${app.id}" role="button" tabindex="0"
|
||||||
aria-label="${app.label}"
|
aria-label="${app.label}"
|
||||||
style="${bgStyle}${cardColorStyle}">
|
style="${bgStyle}">
|
||||||
<i data-lucide="${app.icon}" class="app-icon"
|
${delBtn}
|
||||||
style="stroke:${color}"></i>
|
${iconHTML}
|
||||||
<span class="app-label">${app.label}</span>
|
<span class="app-label">${app.label}</span>
|
||||||
<span class="check-badge" aria-hidden="true"
|
<span class="check-badge" aria-hidden="true"
|
||||||
style="background:${color}">
|
style="background:${color}">
|
||||||
|
|
@ -810,7 +1158,7 @@ function renderApps() {
|
||||||
|
|
||||||
container.querySelectorAll('.app-item').forEach(el => {
|
container.querySelectorAll('.app-item').forEach(el => {
|
||||||
const id = el.dataset.id;
|
const id = el.dataset.id;
|
||||||
const app = APP_DB.find(a => a.id === id);
|
const app = allApps().find(a => a.id === id);
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
if (editMode) toggleApp(id);
|
if (editMode) toggleApp(id);
|
||||||
|
|
@ -825,6 +1173,13 @@ function renderApps() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
container.querySelectorAll('.custom-del-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteCustomApp(btn.dataset.delId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -870,7 +1225,7 @@ document.getElementById('apiKeySave').addEventListener('click', () => {
|
||||||
|
|
||||||
// ── リセット ────────────────────────────────────────────────
|
// ── リセット ────────────────────────────────────────────────
|
||||||
document.getElementById('resetBtn').addEventListener('click', () => {
|
document.getElementById('resetBtn').addEventListener('click', () => {
|
||||||
enabledIds = new Set(APP_DB.map(a => a.id));
|
enabledIds = new Set(allApps().map(a => a.id));
|
||||||
saveEnabled();
|
saveEnabled();
|
||||||
renderApps();
|
renderApps();
|
||||||
showToast('リセットしました');
|
showToast('リセットしました');
|
||||||
|
|
@ -884,6 +1239,110 @@ function showToast(msg) {
|
||||||
setTimeout(() => el.classList.remove('show'), 2200);
|
setTimeout(() => el.classList.remove('show'), 2200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── アプリ追加モーダル ───────────────────────────────────────
|
||||||
|
let selectedIcon = null;
|
||||||
|
|
||||||
|
function buildIconPicker() {
|
||||||
|
const grid = document.getElementById('iconPickerGrid');
|
||||||
|
grid.innerHTML = ICON_PICKER_ICONS.map(name => `
|
||||||
|
<button class="icon-pick-btn${selectedIcon === name ? ' selected' : ''}"
|
||||||
|
data-icon="${name}" type="button" aria-label="${name}">
|
||||||
|
<i data-lucide="${name}" style="width:17px;height:17px;stroke-width:1.5"></i>
|
||||||
|
</button>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
grid.querySelectorAll('.icon-pick-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
selectedIcon = btn.dataset.icon;
|
||||||
|
grid.querySelectorAll('.icon-pick-btn').forEach(b => b.classList.remove('selected'));
|
||||||
|
btn.classList.add('selected');
|
||||||
|
document.getElementById('iconInitialPick').classList.remove('selected');
|
||||||
|
validateAddForm();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAddModal() {
|
||||||
|
selectedIcon = null;
|
||||||
|
document.getElementById('addName').value = '';
|
||||||
|
document.getElementById('addCat').value = '';
|
||||||
|
document.getElementById('addAndroid').value = '';
|
||||||
|
document.getElementById('addIos').value = '';
|
||||||
|
document.getElementById('addWeb').value = '';
|
||||||
|
document.getElementById('addSubmitBtn').disabled = true;
|
||||||
|
document.getElementById('iconInitialPick').classList.remove('selected');
|
||||||
|
document.getElementById('initialPreviewChar').textContent = 'A';
|
||||||
|
|
||||||
|
buildIconPicker();
|
||||||
|
|
||||||
|
document.getElementById('addModalBackdrop').classList.add('open');
|
||||||
|
document.getElementById('addModal').classList.add('open');
|
||||||
|
document.getElementById('addName').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAddModal() {
|
||||||
|
document.getElementById('addModalBackdrop').classList.remove('open');
|
||||||
|
document.getElementById('addModal').classList.remove('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAddForm() {
|
||||||
|
const name = document.getElementById('addName').value.trim();
|
||||||
|
const cat = document.getElementById('addCat').value;
|
||||||
|
const ok = name.length > 0 && cat.length > 0 && selectedIcon !== null;
|
||||||
|
document.getElementById('addSubmitBtn').disabled = !ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('addName').addEventListener('input', () => {
|
||||||
|
const firstChar = document.getElementById('addName').value.trim().charAt(0).toUpperCase() || 'A';
|
||||||
|
document.getElementById('initialPreviewChar').textContent = firstChar || 'A';
|
||||||
|
validateAddForm();
|
||||||
|
});
|
||||||
|
document.getElementById('addCat').addEventListener('change', validateAddForm);
|
||||||
|
|
||||||
|
document.getElementById('iconInitialPick').addEventListener('click', () => {
|
||||||
|
selectedIcon = '_initial';
|
||||||
|
document.getElementById('iconInitialPick').classList.add('selected');
|
||||||
|
document.getElementById('iconPickerGrid').querySelectorAll('.icon-pick-btn')
|
||||||
|
.forEach(b => b.classList.remove('selected'));
|
||||||
|
validateAddForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('addSubmitBtn').addEventListener('click', () => {
|
||||||
|
const name = document.getElementById('addName').value.trim();
|
||||||
|
const cat = document.getElementById('addCat').value;
|
||||||
|
const android = document.getElementById('addAndroid').value.trim() || null;
|
||||||
|
const ios = document.getElementById('addIos').value.trim() || null;
|
||||||
|
const web = document.getElementById('addWeb').value.trim() || null;
|
||||||
|
|
||||||
|
if (!name || !cat || !selectedIcon) return;
|
||||||
|
|
||||||
|
const newApp = {
|
||||||
|
id: `custom-${Date.now()}`,
|
||||||
|
label: name,
|
||||||
|
icon: selectedIcon,
|
||||||
|
cat: cat,
|
||||||
|
android: android,
|
||||||
|
ios: ios,
|
||||||
|
web: web,
|
||||||
|
custom: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
customApps.push(newApp);
|
||||||
|
saveCustomApps();
|
||||||
|
enabledIds.add(newApp.id);
|
||||||
|
saveEnabled();
|
||||||
|
|
||||||
|
closeAddModal();
|
||||||
|
showToast(`${name} を追加しました`);
|
||||||
|
renderApps();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('addAppBtn').addEventListener('click', openAddModal);
|
||||||
|
document.getElementById('addModalClose').addEventListener('click', closeAddModal);
|
||||||
|
document.getElementById('addModalBackdrop').addEventListener('click', closeAddModal);
|
||||||
|
|
||||||
// ── 時計 ────────────────────────────────────────────────────
|
// ── 時計 ────────────────────────────────────────────────────
|
||||||
const DAYS_JP = ['日','月','火','水','木','金','土'];
|
const DAYS_JP = ['日','月','火','水','木','金','土'];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue