feat: custom associations

This commit is contained in:
uncenter 2024-03-11 11:49:16 -04:00
parent 2a0ae82a19
commit c99c5187ab
No known key found for this signature in database
9 changed files with 243 additions and 11 deletions

View File

@ -15,6 +15,21 @@
<option value="mocha">Mocha</option>
</select>
</section>
<section class="associations">
<label>Associations</label>
<div>
<span>File extensions</span>
<ul id="associations-file-extensions"></ul>
</div>
<div>
<span>File names</span>
<ul id="associations-file-names"></ul>
</div>
<div>
<span>Folder names</span>
<ul id="associations-folder-names"></ul>
</div>
</section>
</body>
<script type="module" src="./main.ts"></script>
</html>

View File

@ -1,8 +1,9 @@
import './styles.css';
import type { Flavor } from '@/lib/types';
import type { Associations, Flavor, IconName } from '@/lib/types';
import { flavor } from '@/lib/storage';
import { customAssociations, flavor } from '@/lib/storage';
import { icons } from '@/lib/constants';
async function init() {
const flavorEl = document.querySelector('#flavor') as HTMLSelectElement;
@ -15,6 +16,121 @@ async function init() {
await flavor.setValue(value);
document.documentElement.setAttribute('theme', value);
});
const associations = await customAssociations.getValue();
for (const [key, el] of Object.entries({
fileExtensions: document.querySelector(
'ul#associations-file-extensions',
),
fileNames: document.querySelector('ul#associations-file-names'),
folderNames: document.querySelector('ul#associations-folder-names'),
} as Record<keyof Associations, HTMLUListElement>) as [
keyof Associations,
HTMLUListElement,
][]) {
for (const [association, icon] of Object.entries(associations[key])) {
const li = document.createElement('li');
const inputA = document.createElement('input');
const inputB = document.createElement('input');
const del = document.createElement('button');
del.className = 'delete';
del.innerHTML = icons['x'];
inputA.value = association;
inputA.setAttribute('required', 'true');
inputB.value = icon;
inputB.setAttribute('required', 'true');
li.appendChild(inputA);
li.appendChild(inputB);
li.appendChild(del);
el.appendChild(li);
del.addEventListener('click', async () => {
if (inputA.checkValidity()) {
delete associations[key][association];
await customAssociations.setValue(associations);
li.remove();
}
});
inputA.addEventListener('change', async () => {
if (inputA.checkValidity()) {
delete associations[key][association];
associations[key][inputA.value] = icon;
await customAssociations.setValue(associations);
}
});
inputB.addEventListener('change', async () => {
if (inputB.checkValidity()) {
associations[key][association] = inputB.value as IconName;
await customAssociations.setValue(associations);
}
});
}
function addEmpty() {
const li = document.createElement('li');
const inputA = document.createElement('input');
const inputB = document.createElement('input');
const del = document.createElement('button');
del.className = 'delete';
del.innerHTML = icons['x'];
inputA.setAttribute('required', 'true');
inputB.setAttribute('required', 'true');
li.appendChild(inputA);
li.appendChild(inputB);
li.appendChild(del);
el.appendChild(li);
del.addEventListener('click', async () => {
if (inputA.checkValidity()) {
delete associations[key][inputA.value];
await customAssociations.setValue(associations);
li.remove();
}
});
let addedEmpty = false;
inputA.addEventListener('change', async () => {
if (inputA.checkValidity()) {
delete associations[key][inputA.value];
associations[key][inputA.value] = inputB.value as IconName;
await customAssociations.setValue(associations);
}
if (
!addedEmpty &&
inputA.checkValidity() &&
inputB.checkValidity()
) {
addEmpty();
addedEmpty = true;
}
});
inputB.addEventListener('change', async () => {
if (inputB.checkValidity()) {
associations[key][inputA.value] = inputB.value as IconName;
await customAssociations.setValue(associations);
}
if (
!addedEmpty &&
inputA.checkValidity() &&
inputB.checkValidity()
) {
addEmpty();
addedEmpty = true;
}
});
}
addEmpty();
}
}
init();

View File

@ -119,7 +119,7 @@
}
body {
width: 400px;
width: 600px;
height: max-content;
padding: 1rem;
@ -131,7 +131,8 @@ body {
color: var(--ctp-text) !important;
}
select {
select,
input {
background-color: var(--ctp-base);
color: var(--ctp-text) !important;
border: none;
@ -139,10 +140,15 @@ select {
padding: 0.5rem;
}
select:focus-visible {
select:focus-visible,
input:focus-visible {
outline: 3px solid var(--ctp-mauve);
}
input::placeholder {
color: var(--ctp-subtext0);
}
section {
padding: 1rem;
border-radius: 0.25rem;
@ -153,6 +159,52 @@ section {
align-items: center;
}
section.associations {
flex-direction: column;
align-items: baseline;
gap: 1rem;
}
section.associations div {
display: flex;
flex-direction: column;
}
section.associations div span {
padding-bottom: 0.5rem;
}
section.associations ul {
list-style: none;
padding: 0;
margin: 0;
}
section.associations ul li {
display: flex;
flex-direction: row;
gap: 0.5rem;
padding-top: 0.5rem;
}
button {
color: var(--ctp-text);
background-color: transparent;
appearance: none;
border: none;
padding: 0.25rem;
cursor: pointer;
}
button.delete {
color: var(--ctp-red);
}
button.delete svg {
width: 1.5em;
height: 1.5em;
}
label {
font-weight: 600;
font-size: 1rem;

24
src/lib/associations.ts Normal file
View File

@ -0,0 +1,24 @@
import type { Associations } from './types';
import { associations as json } from '@/vscode-icons.json';
import { customAssociations } from './storage';
export async function getAssociations(): Promise<Associations> {
const custom = await customAssociations.getValue();
return {
languageIds: json.languageIds as Associations['languageIds'],
fileExtensions: {
...json.fileExtensions,
...custom.fileExtensions,
} as Associations['languageIds'],
fileNames: {
...json.fileNames,
...custom.fileNames,
} as Associations['fileNames'],
folderNames: {
...json.folderNames,
...custom.folderNames,
} as Associations['folderNames'],
};
}

View File

@ -18,3 +18,7 @@ export const selectors = {
.react-directory-filename-column > svg,
[aria-label="Parent directory"] svg`,
};
export const icons = {
x: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path></svg>',
};

View File

@ -2,8 +2,8 @@ import type { PublicPath } from 'wxt/browser';
import type { IconName } from '@/lib/types';
import { selectors } from '@/lib/constants';
import { associations } from '@/vscode-icons.json';
import { flavor } from '@/lib/storage';
import { getAssociations } from './associations';
export async function replaceIconInRow(row: HTMLElement) {
const icon = row.querySelector(selectors.icon) as HTMLElement;
@ -39,7 +39,7 @@ export async function replaceIcon(icon: HTMLElement, row: HTMLElement) {
}
}
const iconName = findIconMatch(
const iconName = await findIconMatch(
fileName,
fileExtensions,
isDir,
@ -114,17 +114,19 @@ export async function replaceElementWithIcon(
}
}
function findIconMatch(
async function findIconMatch(
fileName: string,
fileExtensions: string[],
isDir: boolean,
isSubmodule: boolean,
): IconName {
): Promise<IconName> {
// Special parent directory folder icon:
if (fileName === '..') return '_folder';
if (isSubmodule) return 'folder_git';
const associations = await getAssociations();
if (isDir) {
if (fileName in associations.folderNames)
return associations.folderNames[fileName];

View File

@ -1,5 +1,17 @@
import type { Flavor } from './types';
import type { Associations, Flavor } from './types';
export const flavor = storage.defineItem<Flavor>('local:flavor', {
defaultValue: 'mocha',
});
export const customAssociations = storage.defineItem<Associations>(
'local:associations',
{
defaultValue: {
languageIds: {},
fileExtensions: {},
fileNames: {},
folderNames: {},
},
},
);

View File

@ -5,3 +5,10 @@ export type Flavor = 'latte' | 'frappe' | 'macchiato' | 'mocha';
type RemoveIconPrefixAndSuffix<T extends string> =
T extends `/${Flavor}/${infer Rest}.svg` ? Rest : never;
export type IconName = RemoveIconPrefixAndSuffix<PublicPath>;
export type Associations = {
languageIds: Record<string, IconName>;
fileExtensions: Record<string, IconName>;
fileNames: Record<string, IconName>;
folderNames: Record<string, IconName>;
};

View File

@ -36,7 +36,7 @@ export default defineConfig({
);
await hfs.deleteAll(join(PUBLIC_DIR, 'css-variables'));
// Write assocations/config file:
// Write associations/config file:
await hfs.write(
join(__dirname, './src/vscode-icons.json'),
JSON.stringify(