Add "Always" condition type to profiles
- Add AlwaysCondition model and evaluation (always returns true) - Add condition type selector (Always/Application) in profile editor - Show condition type pill on profile cards - Fix misleading empty-conditions text (was "never activate", actually always active) - Add i18n keys for Always condition (en + ru) - Add CSS for condition type selector and description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,9 @@ function createProfileCard(profile, runningTargetIds = new Set()) {
|
||||
condPills = `<span class="stream-card-prop">${t('profiles.conditions.empty')}</span>`;
|
||||
} else {
|
||||
const parts = profile.conditions.map(c => {
|
||||
if (c.condition_type === 'always') {
|
||||
return `<span class="stream-card-prop">✅ ${t('profiles.condition.always')}</span>`;
|
||||
}
|
||||
if (c.condition_type === 'application') {
|
||||
const apps = (c.apps || []).join(', ');
|
||||
const matchLabel = t('profiles.condition.application.match_type.' + (c.match_type || 'running'));
|
||||
@@ -233,45 +236,64 @@ function addProfileConditionRow(condition) {
|
||||
const list = document.getElementById('profile-conditions-list');
|
||||
const row = document.createElement('div');
|
||||
row.className = 'profile-condition-row';
|
||||
|
||||
const appsValue = (condition.apps || []).join('\n');
|
||||
const matchType = condition.match_type || 'running';
|
||||
const condType = condition.condition_type || 'application';
|
||||
|
||||
row.innerHTML = `
|
||||
<div class="condition-header">
|
||||
<span class="condition-type-label">${t('profiles.condition.application')}</span>
|
||||
<select class="condition-type-select">
|
||||
<option value="always" ${condType === 'always' ? 'selected' : ''}>${t('profiles.condition.always')}</option>
|
||||
<option value="application" ${condType === 'application' ? 'selected' : ''}>${t('profiles.condition.application')}</option>
|
||||
</select>
|
||||
<button type="button" class="btn-remove-condition" onclick="this.closest('.profile-condition-row').remove()" title="Remove">✕</button>
|
||||
</div>
|
||||
<div class="condition-fields">
|
||||
<div class="condition-field">
|
||||
<label data-i18n="profiles.condition.application.match_type">${t('profiles.condition.application.match_type')}</label>
|
||||
<select class="condition-match-type">
|
||||
<option value="running" ${matchType === 'running' ? 'selected' : ''}>${t('profiles.condition.application.match_type.running')}</option>
|
||||
<option value="topmost" ${matchType === 'topmost' ? 'selected' : ''}>${t('profiles.condition.application.match_type.topmost')}</option>
|
||||
<option value="topmost_fullscreen" ${matchType === 'topmost_fullscreen' ? 'selected' : ''}>${t('profiles.condition.application.match_type.topmost_fullscreen')}</option>
|
||||
<option value="fullscreen" ${matchType === 'fullscreen' ? 'selected' : ''}>${t('profiles.condition.application.match_type.fullscreen')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="condition-field">
|
||||
<div class="condition-apps-header">
|
||||
<label data-i18n="profiles.condition.application.apps">${t('profiles.condition.application.apps')}</label>
|
||||
<button type="button" class="btn-browse-apps" title="${t('profiles.condition.application.browse')}">${t('profiles.condition.application.browse')}</button>
|
||||
</div>
|
||||
<textarea class="condition-apps" rows="3" placeholder="firefox.exe chrome.exe">${escapeHtml(appsValue)}</textarea>
|
||||
<div class="process-picker" style="display:none">
|
||||
<input type="text" class="process-picker-search" placeholder="${t('profiles.condition.application.search')}" autocomplete="off">
|
||||
<div class="process-picker-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="condition-fields-container"></div>
|
||||
`;
|
||||
|
||||
const browseBtn = row.querySelector('.btn-browse-apps');
|
||||
const picker = row.querySelector('.process-picker');
|
||||
browseBtn.addEventListener('click', () => toggleProcessPicker(picker, row));
|
||||
const typeSelect = row.querySelector('.condition-type-select');
|
||||
const container = row.querySelector('.condition-fields-container');
|
||||
|
||||
const searchInput = row.querySelector('.process-picker-search');
|
||||
searchInput.addEventListener('input', () => filterProcessPicker(picker));
|
||||
function renderFields(type, data) {
|
||||
if (type === 'always') {
|
||||
container.innerHTML = `<small class="condition-always-desc">${t('profiles.condition.always.hint')}</small>`;
|
||||
return;
|
||||
}
|
||||
const appsValue = (data.apps || []).join('\n');
|
||||
const matchType = data.match_type || 'running';
|
||||
container.innerHTML = `
|
||||
<div class="condition-fields">
|
||||
<div class="condition-field">
|
||||
<label>${t('profiles.condition.application.match_type')}</label>
|
||||
<select class="condition-match-type">
|
||||
<option value="running" ${matchType === 'running' ? 'selected' : ''}>${t('profiles.condition.application.match_type.running')}</option>
|
||||
<option value="topmost" ${matchType === 'topmost' ? 'selected' : ''}>${t('profiles.condition.application.match_type.topmost')}</option>
|
||||
<option value="topmost_fullscreen" ${matchType === 'topmost_fullscreen' ? 'selected' : ''}>${t('profiles.condition.application.match_type.topmost_fullscreen')}</option>
|
||||
<option value="fullscreen" ${matchType === 'fullscreen' ? 'selected' : ''}>${t('profiles.condition.application.match_type.fullscreen')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="condition-field">
|
||||
<div class="condition-apps-header">
|
||||
<label>${t('profiles.condition.application.apps')}</label>
|
||||
<button type="button" class="btn-browse-apps" title="${t('profiles.condition.application.browse')}">${t('profiles.condition.application.browse')}</button>
|
||||
</div>
|
||||
<textarea class="condition-apps" rows="3" placeholder="firefox.exe chrome.exe">${escapeHtml(appsValue)}</textarea>
|
||||
<div class="process-picker" style="display:none">
|
||||
<input type="text" class="process-picker-search" placeholder="${t('profiles.condition.application.search')}" autocomplete="off">
|
||||
<div class="process-picker-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const browseBtn = container.querySelector('.btn-browse-apps');
|
||||
const picker = container.querySelector('.process-picker');
|
||||
browseBtn.addEventListener('click', () => toggleProcessPicker(picker, row));
|
||||
const searchInput = container.querySelector('.process-picker-search');
|
||||
searchInput.addEventListener('input', () => filterProcessPicker(picker));
|
||||
}
|
||||
|
||||
renderFields(condType, condition);
|
||||
typeSelect.addEventListener('change', () => {
|
||||
renderFields(typeSelect.value, { apps: [], match_type: 'running' });
|
||||
});
|
||||
|
||||
list.appendChild(row);
|
||||
}
|
||||
@@ -340,10 +362,16 @@ function getProfileEditorConditions() {
|
||||
const rows = document.querySelectorAll('#profile-conditions-list .profile-condition-row');
|
||||
const conditions = [];
|
||||
rows.forEach(row => {
|
||||
const matchType = row.querySelector('.condition-match-type').value;
|
||||
const appsText = row.querySelector('.condition-apps').value.trim();
|
||||
const apps = appsText ? appsText.split('\n').map(a => a.trim()).filter(Boolean) : [];
|
||||
conditions.push({ condition_type: 'application', apps, match_type: matchType });
|
||||
const typeSelect = row.querySelector('.condition-type-select');
|
||||
const condType = typeSelect ? typeSelect.value : 'application';
|
||||
if (condType === 'always') {
|
||||
conditions.push({ condition_type: 'always' });
|
||||
} else {
|
||||
const matchType = row.querySelector('.condition-match-type').value;
|
||||
const appsText = row.querySelector('.condition-apps').value.trim();
|
||||
const apps = appsText ? appsText.split('\n').map(a => a.trim()).filter(Boolean) : [];
|
||||
conditions.push({ condition_type: 'application', apps, match_type: matchType });
|
||||
}
|
||||
});
|
||||
return conditions;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user