Browse Source

feat: Start the ability to star/bookmark a project.

- Currently toggles & stores correctly locally on project. Does not show on other screens.
star-projects
Trent Larson 1 month ago
parent
commit
a6b824cd21
  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/hdnode": "^5.7.0",
"@ethersproject/wallet": "^5.8.0", "@ethersproject/wallet": "^5.8.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6", "@fortawesome/vue-fontawesome": "^3.0.6",
"@jlongster/sql.js": "^1.6.7", "@jlongster/sql.js": "^1.6.7",
@ -6663,6 +6664,17 @@
"node": ">=6" "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": { "node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.7.2", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", "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/hdnode": "^5.7.0",
"@ethersproject/wallet": "^5.8.0", "@ethersproject/wallet": "^5.8.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/vue-fontawesome": "^3.0.6", "@fortawesome/vue-fontawesome": "^3.0.6",
"@jlongster/sql.js": "^1.6.7", "@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; 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.columns,
result.values, result.values,
)[0] as Settings; )[0] as Settings;
if (settings.searchBoxes) { settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
// @ts-expect-error - the searchBoxes field is a string in the DB settings.starredProjectIds = parseJsonField(settings.starredProjectIds, []);
settings.searchBoxes = JSON.parse(settings.searchBoxes);
}
return settings; return settings;
} }
} }
@ -138,10 +136,11 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
// Merge settings // Merge settings
const settings = { ...defaultSettings, ...overrideSettingsFiltered }; const settings = { ...defaultSettings, ...overrideSettingsFiltered };
// Handle searchBoxes parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []); settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
} settings.starredProjectIds = parseJsonField(
settings.starredProjectIds,
[],
);
return settings; return settings;
} catch (error) { } catch (error) {

5
src/db/tables/settings.ts

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

10
src/libs/fontawesome.ts

@ -69,8 +69,8 @@ import {
faPersonCircleCheck, faPersonCircleCheck,
faPersonCircleQuestion, faPersonCircleQuestion,
faPlus, faPlus,
faQuestion,
faQrcode, faQrcode,
faQuestion,
faRightFromBracket, faRightFromBracket,
faRotate, faRotate,
faShareNodes, faShareNodes,
@ -79,6 +79,7 @@ import {
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faSquarePlus, faSquarePlus,
faStar,
faTrashCan, faTrashCan,
faTriangleExclamation, faTriangleExclamation,
faUser, faUser,
@ -86,6 +87,9 @@ import {
faXmark, faXmark,
} from "@fortawesome/free-solid-svg-icons"; } 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 // Initialize Font Awesome library with all required icons
library.add( library.add(
faArrowDown, faArrowDown,
@ -153,14 +157,16 @@ library.add(
faPlus, faPlus,
faQrcode, faQrcode,
faQuestion, faQuestion,
faRotate,
faRightFromBracket, faRightFromBracket,
faRotate,
faShareNodes, faShareNodes,
faSpinner, faSpinner,
faSquare, faSquare,
faSquareCaretDown, faSquareCaretDown,
faSquareCaretUp, faSquareCaretUp,
faSquarePlus, faSquarePlus,
faStar,
faStarRegular,
faTrashCan, faTrashCan,
faTriangleExclamation, faTriangleExclamation,
faUser, faUser,

3
src/views/AccountViewView.vue

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

100
src/views/ProjectViewView.vue

@ -33,6 +33,20 @@
class="text-sm text-slate-500 ml-2 mb-1" class="text-sm text-slate-500 ml-2 mb-1"
/> />
</button> </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> </h2>
</div> </div>
</div> </div>
@ -58,13 +72,13 @@
icon="user" icon="user"
class="fa-fw text-slate-400" class="fa-fw text-slate-400"
></font-awesome> ></font-awesome>
<span class="truncate inline-block max-w-[calc(100%-2rem)]"> <span class="truncate max-w-[calc(100%-2rem)] ml-1">
{{ issuerInfoObject?.displayName }} {{ issuerInfoObject?.displayName }}
</span> </span>
<span <span
v-if="!serverUtil.isHiddenDid(issuer)" v-if="!serverUtil.isHiddenDid(issuer)"
class="inline-flex items-center" class="inline-flex items-center ml-1"
> >
<router-link <router-link
:to="{ :to="{
@ -657,6 +671,7 @@ import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
/** /**
* Project View Component * Project View Component
* @author Matthew Raymer * @author Matthew Raymer
@ -742,6 +757,8 @@ export default class ProjectViewView extends Vue {
givesProvidedByHitLimit = false; givesProvidedByHitLimit = false;
givesTotalsByUnit: Array<{ unit: string; amount: number }> = []; givesTotalsByUnit: Array<{ unit: string; amount: number }> = [];
imageUrl = ""; imageUrl = "";
/** Whether this project is starred by the user */
isStarred = false;
/** Project issuer DID */ /** Project issuer DID */
issuer = ""; issuer = "";
/** Cached issuer information */ /** Cached issuer information */
@ -841,6 +858,15 @@ export default class ProjectViewView extends Vue {
} }
this.loadProject(this.projectId, this.activeDid); this.loadProject(this.projectId, this.activeDid);
this.loadTotals(); 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 { } else {
logger.error("Got error submitting the confirmation:", result); logger.error("Got error submitting the confirmation:", result);
const message = const message =
(result.error?.error as string) || (result.error as string) ||
"There was a problem submitting the confirmation."; "There was a problem submitting the confirmation.";
this.$notify( this.$notify(
{ {
@ -1563,5 +1589,73 @@ export default class ProjectViewView extends Vue {
this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0 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> </script>

4
src/views/QuickActionBvcBeginView.vue

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

Loading…
Cancel
Save