Compare commits

..

6 Commits

Author SHA1 Message Date
Bauke 913e8f0da9
Version 0.3.0! 2022-10-25 13:23:14 +02:00
Bauke ee012aea1d
Add move buttons. 2022-10-25 13:08:53 +02:00
Bauke f6571b895b
Fix JS pass by copy/reference issue. 2022-10-25 12:55:07 +02:00
Bauke e591db7bcf
Add move item functionality to Settings. 2022-10-25 12:28:37 +02:00
Bauke d4400c5c31
Update (de)serializing test. 2022-10-25 12:25:41 +02:00
Bauke c72959841f
Add a sortIndex to items. 2022-10-25 12:25:23 +02:00
11 changed files with 147 additions and 23 deletions

View File

@ -6,7 +6,7 @@
[![Get Queue for Chrome](./images/chrome-web-store.png)](https://chrome.google.com/webstore/detail/queue/epnbikemcmienphlfmidkimpjnmohcbl) [![Get Queue for Chrome](./images/chrome-web-store.png)](https://chrome.google.com/webstore/detail/queue/epnbikemcmienphlfmidkimpjnmohcbl)
[![Get Queue for Edge](./images/microsoft.png)](https://microsoftedge.microsoft.com/addons/detail/queue/aanjampfdpcnhoeglmfefmmegdbifaak) [![Get Queue for Edge](./images/microsoft.png)](https://microsoftedge.microsoft.com/addons/detail/queue/aanjampfdpcnhoeglmfefmmegdbifaak)
![Latest Queue screenshot](./images/queue-version-0-2-2.png) ![Latest Queue screenshot](./images/queue-version-0-3-0.png)
## Wiki ## Wiki

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -6,7 +6,7 @@ export default function createManifest(
const manifest: Record<string, unknown> = { const manifest: Record<string, unknown> = {
name: 'Queue', name: 'Queue',
description: 'A WebExtension for queueing links.', description: 'A WebExtension for queueing links.',
version: '0.2.6', version: '0.3.0',
permissions: ['contextMenus', 'storage'], permissions: ['contextMenus', 'storage'],
options_ui: { options_ui: {
page: 'options/index.html', page: 'options/index.html',

View File

@ -23,6 +23,12 @@ export class PageMain extends Component<Props, State> {
}; };
} }
moveItem = async (id: number, direction: Queue.MoveDirection) => {
const {settings} = this.props;
await settings.moveQueueItem(id, direction);
this.setState({queue: this.props.settings.queue});
};
removeItem = async (id: number) => { removeItem = async (id: number) => {
const {settings} = this.props; const {settings} = this.props;
await settings.removeQueueItem(id); await settings.removeQueueItem(id);
@ -34,9 +40,16 @@ export class PageMain extends Component<Props, State> {
const isFirefox = import.meta.env.VITE_BROWSER === 'firefox'; const isFirefox = import.meta.env.VITE_BROWSER === 'firefox';
const queueItems = this.state.queue const queueItems = this.state.queue
.sort((a, b) => a.added.getTime() - b.added.getTime()) .sort((a, b) => a.sortIndex - b.sortIndex)
.map( .map(
(item) => html`<${queueItem} item=${item} remove=${this.removeItem} />`, (item) =>
html`
<${queueItem}
item=${item}
move=${this.moveItem}
remove=${this.removeItem}
/>
`,
); );
if (queueItems.length === 0) { if (queueItems.length === 0) {
@ -114,12 +127,35 @@ export class PageMain extends Component<Props, State> {
type ItemProps = { type ItemProps = {
item: Queue.Item; item: Queue.Item;
move?: (id: number, direction: Queue.MoveDirection) => Promise<void>;
remove?: (id: number) => Promise<void>; remove?: (id: number) => Promise<void>;
}; };
function queueItem(props: ItemProps): HtmComponent { function queueItem(props: ItemProps): HtmComponent {
const added = props.item.added.toLocaleString(); const added = props.item.added.toLocaleString();
const {id, text, url} = props.item; const {id, text, url} = props.item;
const move = [];
if (props.move !== undefined) {
const moveButtons: Array<[string, Queue.MoveDirection]> = [
['↑', 'up'],
['↓', 'down'],
];
move.push(
...moveButtons.map(
([text, direction]) =>
html`
<button
title="Move item ${direction}"
onClick=${async () => props.move!(id, direction)}
>
${text}
</button>
`,
),
);
}
let remove; let remove;
if (props.remove !== undefined) { if (props.remove !== undefined) {
remove = html` remove = html`
@ -141,7 +177,7 @@ function queueItem(props: ItemProps): HtmComponent {
<${PrivacyLink} attributes=${{href: url}}>${text ?? url}<//> <${PrivacyLink} attributes=${{href: url}}>${text ?? url}<//>
</p> </p>
<div class="buttons">${remove}</div> <div class="buttons">${move}${remove}</div>
<p> <p>
<time datetime=${added} title="Link queued on ${added}."> <time datetime=${added} title="Link queued on ${added}.">

View File

@ -30,23 +30,34 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
.confirm-button { .buttons {
align-items: center; display: grid;
background-color: var(--la-1); gap: 4px;
border: none; grid-template-columns: repeat(3, 1fr);
color: var(--df-1);
cursor: pointer;
display: flex;
flex-direction: column;
font-weight: bold;
height: 2.5rem;
justify-content: center;
padding: 0;
width: 2.5rem;
&.confirm { button {
background-color: var(--df-1); align-items: center;
color: var(--la-1); background-color: var(--da-3);
border: none;
color: var(--db-1);
cursor: pointer;
display: flex;
flex-direction: column;
font-weight: bold;
height: 2.5rem;
justify-content: center;
padding: 0;
width: 2.5rem;
&.confirm-button {
background-color: var(--la-1);
color: var(--df-1);
&.confirm {
background-color: var(--df-1);
color: var(--la-1);
}
}
} }
} }
} }

View File

@ -14,6 +14,23 @@ export const dataMigrations: Array<Migration<string>> = [
migrated[key] = item; migrated[key] = item;
} }
return migrated;
},
},
{
version: '0.3.0',
async migrate(data: Record<string, any>) {
const migrated: Record<string, any> = {
version: '0.3.0',
};
for (const [key, value] of Object.entries<Queue.Item>(data)) {
if (key.startsWith('qi')) {
migrated[key] = value;
migrated[key].sortIndex = value.id;
}
}
return migrated; return migrated;
}, },
}, },

View File

@ -42,6 +42,7 @@ export class Settings {
const item: Queue.Item = { const item: Queue.Item = {
added: new Date(), added: new Date(),
id, id,
sortIndex: id,
text, text,
url, url,
}; };
@ -51,12 +52,40 @@ export class Settings {
[`qi${id}`]: { [`qi${id}`]: {
added: item.added.toISOString(), added: item.added.toISOString(),
id, id,
sortIndex: id,
text, text,
url, url,
}, },
}); });
} }
public async moveQueueItem(
id: number,
direction: Queue.MoveDirection,
): Promise<void> {
const targetItem = this.queue.find((item) => item.id === id);
if (targetItem === undefined) {
throw new Error(`Failed to move item with ID: ${id}`);
}
const previousIndex = targetItem.sortIndex;
let targetIndex = previousIndex;
if (direction === 'down') {
targetIndex += 1;
} else if (direction === 'up') {
targetIndex -= 1;
}
const existingItem = this.queue.find(
(item) => item.sortIndex === targetIndex,
);
if (existingItem !== undefined) {
existingItem.sortIndex = previousIndex;
targetItem.sortIndex = targetIndex;
await this.save();
}
}
public newQueueItemId(): number { public newQueueItemId(): number {
const item = this.queue.sort((a, b) => b.id - a.id)[0]; const item = this.queue.sort((a, b) => b.id - a.id)[0];
return item === undefined ? 1 : item.id + 1; return item === undefined ? 1 : item.id + 1;

3
source/types.d.ts vendored
View File

@ -22,8 +22,11 @@ declare global {
type Item = { type Item = {
added: Date; added: Date;
id: number; id: number;
sortIndex: number;
text: string; text: string;
url: string; url: string;
}; };
type MoveDirection = 'up' | 'down';
} }
} }

View File

@ -13,7 +13,7 @@ const queueItemSample: Queue.Item = {
id: 1, id: 1,
text: 'Sample', text: 'Sample',
url: 'https://example.org', url: 'https://example.org',
}; } as unknown as Queue.Item;
test('dataMigrations happy path', async (t) => { test('dataMigrations happy path', async (t) => {
let data: Record<string, any> = { let data: Record<string, any> = {
@ -37,7 +37,14 @@ test('dataMigrations unhappy path', async (t) => {
}); });
test('Serializing & Deserializing Queue', (t) => { test('Serializing & Deserializing Queue', (t) => {
const serialized = serializeQueue([queueItemSample]); const sample: Queue.Item = {
added: queueItemSample.added,
id: queueItemSample.id,
sortIndex: queueItemSample.id,
text: queueItemSample.text,
url: queueItemSample.url,
};
const serialized = serializeQueue([sample]);
t.snapshot(serialized, 'Serialized'); t.snapshot(serialized, 'Serialized');
serialized.extra = 'Extra'; serialized.extra = 'Extra';

View File

@ -18,6 +18,19 @@ Generated by [AVA](https://avajs.dev).
version: '0.1.7', version: '0.1.7',
} }
> Migration 0.3.0
{
qi1: {
added: Date 2022-03-02 16:00:00 UTC {},
id: 1,
sortIndex: 1,
text: 'Sample',
url: 'https://example.org',
},
version: '0.3.0',
}
## dataMigrations unhappy path ## dataMigrations unhappy path
> Migration 0.1.7 > Migration 0.1.7
@ -26,6 +39,12 @@ Generated by [AVA](https://avajs.dev).
version: '0.1.7', version: '0.1.7',
} }
> Migration 0.3.0
{
version: '0.3.0',
}
## Serializing & Deserializing Queue ## Serializing & Deserializing Queue
> Serialized > Serialized
@ -34,6 +53,7 @@ Generated by [AVA](https://avajs.dev).
qi1: { qi1: {
added: '2022-03-02T16:00:00.000Z', added: '2022-03-02T16:00:00.000Z',
id: 1, id: 1,
sortIndex: 1,
text: 'Sample', text: 'Sample',
url: 'https://example.org', url: 'https://example.org',
}, },
@ -45,6 +65,7 @@ Generated by [AVA](https://avajs.dev).
{ {
added: Date 2022-03-02 16:00:00 UTC {}, added: Date 2022-03-02 16:00:00 UTC {},
id: 1, id: 1,
sortIndex: 1,
text: 'Sample', text: 'Sample',
url: 'https://example.org', url: 'https://example.org',
}, },