Compare commits

...

1 Commits

Author SHA1 Message Date
Trent Larson a6b824cd21 feat: Start the ability to star/bookmark a project. 1 month ago
  1. 12
      package-lock.json
  2. 1
      package.json
  3. 6
      src/db-sql/migration.ts
  4. 13
      src/db/databaseUtil.ts
  5. 5
      src/db/tables/settings.ts
  6. 10
      src/libs/fontawesome.ts
  7. 3
      src/views/AccountViewView.vue
  8. 100
      src/views/ProjectViewView.vue
  9. 4
      src/views/QuickActionBvcBeginView.vue

12
package-lock.json

@ -24,6 +24,7 @@
"@ethersproject/hdnode": "^5.7.0",
"@ethersproject/wallet": "^5.8.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6",
"@jlongster/sql.js": "^1.6.7",
@ -6663,6 +6664,17 @@
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz",
"integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",

1
package.json

@ -62,6 +62,7 @@
"@ethersproject/hdnode": "^5.7.0",
"@ethersproject/wallet": "^5.8.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6",
"@jlongster/sql.js": "^1.6.7",

6
src/db-sql/migration.ts

@ -124,6 +124,12 @@ const MIGRATIONS = [
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
`,
},
{
name: "003_add_starredProjectIds_to_settings",
sql: `
ALTER TABLE settings ADD COLUMN starredProjectIds TEXT;
`,
},
];
/**

13
src/db/databaseUtil.ts

@ -89,10 +89,8 @@ export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {
result.columns,
result.values,
)[0] as Settings;
if (settings.searchBoxes) {
// @ts-expect-error - the searchBoxes field is a string in the DB
settings.searchBoxes = JSON.parse(settings.searchBoxes);
}
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
settings.starredProjectIds = parseJsonField(settings.starredProjectIds, []);
return settings;
}
}
@ -138,10 +136,11 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
// Merge settings
const settings = { ...defaultSettings, ...overrideSettingsFiltered };
// Handle searchBoxes parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
}
settings.starredProjectIds = parseJsonField(
settings.starredProjectIds,
[],
);
return settings;
} catch (error) {

5
src/db/tables/settings.ts

@ -58,6 +58,10 @@ export type Settings = {
showContactGivesInline?: boolean; // Display contact inline or not
showGeneralAdvanced?: boolean; // Show advanced features which don't have their own flag
showShortcutBvc?: boolean; // Show shortcut for Bountiful Voluntaryist Community actions
// List of starred project IDs, which are recommended to be handleIds
starredProjectIds?: Array<string>;
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
warnIfProdServer?: boolean; // Warn if using a production server
warnIfTestServer?: boolean; // Warn if using a testing server
@ -67,6 +71,7 @@ export type Settings = {
// type of settings where the searchBoxes are JSON strings instead of objects
export type SettingsWithJsonStrings = Settings & {
searchBoxes: string;
starredProjectIds: string;
};
export function checkIsAnyFeedFilterOn(settings: Settings): boolean {

10
src/libs/fontawesome.ts

@ -69,8 +69,8 @@ import {
faPersonCircleCheck,
faPersonCircleQuestion,
faPlus,
faQuestion,
faQrcode,
faQuestion,
faRightFromBracket,
faRotate,
faShareNodes,
@ -79,6 +79,7 @@ import {
faSquareCaretDown,
faSquareCaretUp,
faSquarePlus,
faStar,
faTrashCan,
faTriangleExclamation,
faUser,
@ -86,6 +87,9 @@ import {
faXmark,
} from "@fortawesome/free-solid-svg-icons";
// these are referenced differently, eg. ":icon='['far', 'star']'" as in ProjectViewView.vue
import { faStar as faStarRegular } from "@fortawesome/free-regular-svg-icons";
// Initialize Font Awesome library with all required icons
library.add(
faArrowDown,
@ -153,14 +157,16 @@ library.add(
faPlus,
faQrcode,
faQuestion,
faRotate,
faRightFromBracket,
faRotate,
faShareNodes,
faSpinner,
faSquare,
faSquareCaretDown,
faSquareCaretUp,
faSquarePlus,
faStar,
faStarRegular,
faTrashCan,
faTriangleExclamation,
faUser,

3
src/views/AccountViewView.vue

@ -1253,7 +1253,8 @@ export default class AccountViewView extends Vue {
this.hideRegisterPromptOnNewContact =
!!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings?.isRegistered;
this.isSearchAreasSet = !!settings.searchBoxes;
this.isSearchAreasSet =
!!settings.searchBoxes && settings.searchBoxes.length > 0;
this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
this.notifyingReminder = !!settings.notifyingReminderTime;

100
src/views/ProjectViewView.vue

@ -33,6 +33,20 @@
class="text-sm text-slate-500 ml-2 mb-1"
/>
</button>
<button
:title="
isStarred
? 'Remove from starred projects'
: 'Add to starred projects'
"
@click="toggleStar()"
>
<font-awesome
:icon="isStarred ? 'star' : ['far', 'star']"
:class="isStarred ? 'text-yellow-500' : 'text-slate-500'"
class="text-sm ml-2 mb-1"
/>
</button>
</h2>
</div>
</div>
@ -58,13 +72,13 @@
icon="user"
class="fa-fw text-slate-400"
></font-awesome>
<span class="truncate inline-block max-w-[calc(100%-2rem)]">
<span class="truncate max-w-[calc(100%-2rem)] ml-1">
{{ issuerInfoObject?.displayName }}
</span>
<span
v-if="!serverUtil.isHiddenDid(issuer)"
class="inline-flex items-center"
class="inline-flex items-center ml-1"
>
<router-link
:to="{
@ -657,6 +671,7 @@ import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { useClipboard } from "@vueuse/core";
/**
* Project View Component
* @author Matthew Raymer
@ -742,6 +757,8 @@ export default class ProjectViewView extends Vue {
givesProvidedByHitLimit = false;
givesTotalsByUnit: Array<{ unit: string; amount: number }> = [];
imageUrl = "";
/** Whether this project is starred by the user */
isStarred = false;
/** Project issuer DID */
issuer = "";
/** Cached issuer information */
@ -841,6 +858,15 @@ export default class ProjectViewView extends Vue {
}
this.loadProject(this.projectId, this.activeDid);
this.loadTotals();
// Check if this project is starred when settings are loaded
if (this.projectId && settings.starredProjectIds) {
const starredIds: string[] = databaseUtil.parseJsonField(
settings.starredProjectIds,
[],
);
this.isStarred = starredIds.includes(this.projectId);
}
}
/**
@ -1483,7 +1509,7 @@ export default class ProjectViewView extends Vue {
} else {
logger.error("Got error submitting the confirmation:", result);
const message =
(result.error?.error as string) ||
(result.error as string) ||
"There was a problem submitting the confirmation.";
this.$notify(
{
@ -1563,5 +1589,73 @@ export default class ProjectViewView extends Vue {
this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0
);
}
/**
* Toggle the starred status of the current project
*/
async toggleStar() {
if (!this.projectId) return;
try {
if (!this.isStarred) {
// Add to starred projects
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const starredIds: string[] = databaseUtil.parseJsonField(
settings.starredProjectIds,
[],
);
if (!starredIds.includes(this.projectId)) {
const newStarredIds = [...starredIds, this.projectId];
const newIdsParam = JSON.stringify(newStarredIds);
const result = await databaseUtil.updateDidSpecificSettings(
this.activeDid,
// @ts-expect-error until we use SettingsWithJsonString properly
{ starredProjectIds: newIdsParam },
);
if (!result) {
// eslint-disable-next-line no-console
console.log(
"Still getting a bad result from SQL update to star a project.",
);
}
}
this.isStarred = true;
} else {
// Remove from starred projects
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const starredIds: string[] = databaseUtil.parseJsonField(
settings.starredProjectIds,
[],
);
const updatedIds = starredIds.filter((id) => id !== this.projectId);
const newIdsParam = JSON.stringify(updatedIds);
const result = await databaseUtil.updateDidSpecificSettings(
this.activeDid,
// @ts-expect-error until we use SettingsWithJsonString properly
{ starredProjectIds: newIdsParam },
);
if (!result) {
// eslint-disable-next-line no-console
console.log(
"Still getting a bad result from SQL update to unstar a project.",
);
}
this.isStarred = false;
}
} catch (error) {
logger.error("Error toggling star status:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "Failed to update starred status. Please try again.",
},
3000,
);
}
}
}
</script>

4
src/views/QuickActionBvcBeginView.vue

@ -153,9 +153,7 @@ export default class QuickActionBvcBeginView extends Vue {
group: "alert",
type: "danger",
title: "Error",
text:
timeResult?.error ||
"There was an error sending the time.",
text: timeResult?.error || "There was an error sending the time.",
},
5000,
);

Loading…
Cancel
Save