Skip to content

Commit

Permalink
Merge pull request #147 from native-land-digital/feature/audio-pronun…
Browse files Browse the repository at this point in the history
…ciation

Feature/audio pronunciation
  • Loading branch information
tempranova authored Nov 11, 2024
2 parents 51452a2 + 4ec7f15 commit b37133f
Show file tree
Hide file tree
Showing 16 changed files with 243 additions and 25 deletions.
8 changes: 7 additions & 1 deletion prisma/kysely/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export type Entry = {
slug: string | null;
color: string | null;
sources: string | null;
pronunciation: string | null;
disclaimer: string | null;
category: string | null;
published: Generated<boolean>;
Expand Down Expand Up @@ -107,6 +106,12 @@ export type Polygon = {
id: Generated<number>;
entryId: number;
};
export type Pronunciation = {
id: Generated<number>;
url: string | null;
text: string | null;
entryId: number;
};
export type Relation = {
id: Generated<number>;
description: string | null;
Expand Down Expand Up @@ -154,6 +159,7 @@ export type DB = {
PermissionEntity: PermissionEntity;
Point: Point;
Polygon: Polygon;
Pronunciation: Pronunciation;
Relation: Relation;
User: User;
UsersOnIssues: UsersOnIssues;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Warnings:
- You are about to drop the column `pronunciation` on the `Entry` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Entry" DROP COLUMN "pronunciation";

-- CreateTable
CREATE TABLE "Pronunciation" (
"id" SERIAL NOT NULL,
"url" TEXT NOT NULL,
"text" TEXT,
"entryId" INTEGER NOT NULL,

CONSTRAINT "Pronunciation_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Pronunciation_entryId_key" ON "Pronunciation"("entryId");

-- AddForeignKey
ALTER TABLE "Pronunciation" ADD CONSTRAINT "Pronunciation_entryId_fkey" FOREIGN KEY ("entryId") REFERENCES "Entry"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- DropIndex
DROP INDEX "Pronunciation_entryId_key";

-- AlterTable
ALTER TABLE "Pronunciation" ALTER COLUMN "url" DROP NOT NULL;
10 changes: 9 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ datasource db {
directUrl = env("DIRECT_URL")
}

model Pronunciation {
id Int @id @default(autoincrement())
url String?
text String?
entryId Int
entry Entry @relation(fields: [entryId], references: [id], onDelete: Cascade)
}

model Greeting {
id Int @id @default(autoincrement())
url String
Expand Down Expand Up @@ -96,7 +104,7 @@ model Entry {
slug String? @unique
color String?
sources String?
pronunciation String?
pronunciation Pronunciation[]
disclaimer String?
category String?
published Boolean @default(false)
Expand Down
1 change: 0 additions & 1 deletion prisma/seed/seed-aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ async function main() {
sources : row.sources,
category : row.category,
published : true,
pronunciation : row.pronunciation ? row.pronunciation : "",
websites : {
createMany : {
data : row.websites.map(website => {
Expand Down
1 change: 0 additions & 1 deletion prisma/seed/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ async function main() {
sources : row.sources,
category : row.category,
published : true,
pronunciation : row.pronunciation ? row.pronunciation : "",
websites : {
createMany : {
data : row.websites.map(website => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default async function Page({ params : { locale, id } }) {
.leftJoin('Line', 'Line.entryId', 'Entry.id')
.leftJoin('Point', 'Point.entryId', 'Entry.id')
.select((eb) => [
'Entry.id', 'Entry.name', 'Entry.category', 'Entry.slug', 'Entry.color', 'Entry.published', 'Entry.sources', 'Entry.disclaimer', 'Entry.pronunciation', 'Entry.createdAt', 'Entry.updatedAt',
'Entry.id', 'Entry.name', 'Entry.category', 'Entry.slug', 'Entry.color', 'Entry.published', 'Entry.sources', 'Entry.disclaimer', 'Entry.createdAt', 'Entry.updatedAt',
eb.fn('COALESCE', [
eb.fn('ST_AsGeoJSON', 'Polygon.geometry'),
eb.fn('ST_AsGeoJSON', 'Line.geometry'),
Expand All @@ -32,6 +32,11 @@ export default async function Page({ params : { locale, id } }) {
.when('Point.entryId', 'is not', null).then('Point')
.end()
.as('geometry_type'),
jsonArrayFrom(
eb.selectFrom('Pronunciation')
.select(['id', 'url', 'text'])
.whereRef('Pronunciation.entryId', '=', 'Entry.id')
).as('pronunciations'),
jsonArrayFrom(
eb.selectFrom('Greeting')
.select(['id', 'url', 'text', 'translation', 'usage', 'parentId'])
Expand Down
40 changes: 34 additions & 6 deletions src/app/[locale]/(public)/maps/[category]/[slug]/page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { setLocaleCache, getTranslations } from '@/i18n/server-i18n';
import { notFound } from 'next/navigation';

import Map from '@/components/maps/map';
import Pronunciations from '@/components/maps/pronunciations';
import Greetings from '@/components/maps/greetings';
import Websites from '@/components/maps/websites';
import Related from '@/components/maps/related';
Expand Down Expand Up @@ -58,6 +59,7 @@ export default async function Page({ params : { locale, category, slug }}) {

setLocaleCache(locale);
const t = await getTranslations('Maps');
const tDash = await getTranslations('Dashboard');

let categoryToSearch = category;
if(category === 'greetings') {
Expand All @@ -70,8 +72,13 @@ export default async function Page({ params : { locale, category, slug }}) {
.where('published', '=', true)
.leftJoin('Polygon', 'Polygon.entryId', 'Entry.id')
.select((eb) => [
'Entry.id', 'Entry.name', 'Entry.category', 'Entry.slug', 'Entry.sources', 'Entry.pronunciation', 'Entry.disclaimer', 'Entry.createdAt', 'Entry.updatedAt',
'Entry.id', 'Entry.name', 'Entry.category', 'Entry.slug', 'Entry.sources', 'Entry.disclaimer', 'Entry.createdAt', 'Entry.updatedAt',
eb.fn('ST_AsGeoJSON', 'Polygon.geometry').as('geometry'),
jsonArrayFrom(
eb.selectFrom('Pronunciation')
.select(['id', 'url', 'text'])
.whereRef('Pronunciation.entryId', '=', 'Entry.id')
).as('pronunciations'),
jsonArrayFrom(
eb.selectFrom('Greeting')
.select(['id', 'url', 'text', 'translation', 'usage', 'parentId'])
Expand Down Expand Up @@ -121,19 +128,34 @@ export default async function Page({ params : { locale, category, slug }}) {
notFound();
}

console.log(entry)

return (
<div className="font-[sans-serif] bg-white pb-5">
<SubHeader title={entry.name} crumbs={[{ url : "/maps", title : "Maps" }, { url : `/maps/${category}`, title : category }]} />
<div className="grid gap-5 grid-cols-1 md:grid-cols-3 min-h-screen w-full md:w-2/3 px-5 md:px-0 m-auto -mt-12 text-black">
<Sidebar picks={3}>
<ol className="hidden md:block list-inside text-gray-400">
<li className="mb-2.5"><a href="#map">{t('map')}</a></li>
<li className="mb-2.5"><a href="#websites">{t('websites')}</a></li>
<li className="mb-2.5"><a href="#greetings">{t('greetings')}</a></li>
<li className="mb-2.5"><a href="#media">{t('media')}</a></li>
{entry.websites.length > 0 ?
<li className="mb-2.5"><a href="#websites">{t('websites')}</a></li>
: false}
{entry.pronunciations.length > 0 ?
<li className="mb-2.5"><a href="#pronunciations">{tDash('pronunciations')}</a></li>
: false}
{entry.greetings.length > 0 ?
<li className="mb-2.5"><a href="#greetings">{t('greetings')}</a></li>
: false}
{entry.media.length > 0 ?
<li className="mb-2.5"><a href="#media">{t('media')}</a></li>
: false}
<li className="mb-2.5"><a href="#sources">{t('sources')}</a></li>
<li className="mb-2.5"><a href="#related-maps">{t('related')}</a></li>
<li className="mb-2.5"><a href="#changelog">{t('changelog')}</a></li>
{entry.relatedTo.length > 0 || entry.relatedFrom.length > 0 ?
<li className="mb-2.5"><a href="#related-maps">{t('related')}</a></li>
: false}
{entry.changelog.length > 0 ?
<li className="mb-2.5"><a href="#changelog">{t('changelog')}</a></li>
: false}
<li className="mb-2.5"><a href="#send-correction">{t('correction')}</a></li>
</ol>
<span />
Expand All @@ -148,6 +170,12 @@ export default async function Page({ params : { locale, category, slug }}) {
<Websites websites={entry.websites} />
</section>
: false}
{entry.pronunciations.length > 0 ?
<section className="mt-5">
<h3 className="text-xl font-bold mb-3" id="pronunciations">{tDash('pronunciations')}</h3>
<Pronunciations pronunciations={entry.pronunciations} />
</section>
: false}
{entry.greetings.length > 0 ?
<section className="hidden mt-5">
<h3 className="text-xl font-bold mb-3" id="greetings">{t('greetings')}</h3>
Expand Down
47 changes: 46 additions & 1 deletion src/app/api/entry/[id]/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,46 @@ export const PATCH = async (req, route) => {
delete body.geometry;
delete body.geometry_type;

// Deleting, updating, adding pronunciation
const pronunciations = body.pronunciations;
const updatedPronunciationIds = pronunciations.map(pronunciation => { return pronunciation.id; });
if(updatedPronunciationIds.length > 0) {
await trx.deleteFrom('Pronunciation')
.where('entryId', '=', parseInt(entryId))
.where('id', 'not in', updatedPronunciationIds)
.execute();
} else {
await trx.deleteFrom('Pronunciation')
.where('entryId', '=', parseInt(entryId))
.execute();
}

for (const pronunciation of pronunciations) {
if (pronunciation.id) {
await trx.updateTable('Pronunciation')
.set({
url: pronunciation.url,
text: pronunciation.text,
})
.where('id', '=', pronunciation.id)
.execute();
}
}

const newPronunciations = pronunciations.filter(pronunciation => !pronunciation.id);
if (newPronunciations.length > 0) {
await trx.insertInto('Pronunciation')
.values(
newPronunciations.map(pronunciation => ({
entryId : parseInt(entryId),
url: pronunciation.url,
text: pronunciation.text,
}))
)
.execute();
}
delete body.pronunciations;

// Deleting, updating, adding websites
const websites = body.websites;
const updatedWebsiteIds = websites.map(website => { return website.id; });
Expand Down Expand Up @@ -325,8 +365,13 @@ export const PATCH = async (req, route) => {
.where('Entry.id', '=', parseInt(entryId))
.leftJoin('Polygon', 'Polygon.entryId', 'Entry.id')
.select((eb) => [
'Entry.id', 'Entry.name', 'Entry.category', 'Entry.slug', 'Entry.color', 'Entry.published', 'Entry.sources', 'Entry.disclaimer', 'Entry.pronunciation', 'Entry.createdAt', 'Entry.updatedAt',
'Entry.id', 'Entry.name', 'Entry.category', 'Entry.slug', 'Entry.color', 'Entry.published', 'Entry.sources', 'Entry.disclaimer', 'Entry.createdAt', 'Entry.updatedAt',
eb.fn('ST_AsGeoJSON', 'Polygon.geometry').as('geometry'),
jsonArrayFrom(
eb.selectFrom('Pronunciation')
.select(['id', 'url', 'text'])
.whereRef('Pronunciation.entryId', '=', 'Entry.id')
).as('pronunciations'),
jsonArrayFrom(
eb.selectFrom('Greeting')
.select(['id', 'url', 'text', 'translation', 'usage', 'parentId'])
Expand Down
15 changes: 7 additions & 8 deletions src/components/dashboard/edit-entry.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Link from 'next/link'

import MainMap from '@/components/dashboard/editors/map-editor';
import WYSIWYGEDitor from '@/components/dashboard/editors/wysiwyg-editor';
import PronunciationsEditor from '@/components/dashboard/editors/pronunciations-editor';
import GreetingsEditor from '@/components/dashboard/editors/greeting-editor';
import MediaEditor from '@/components/dashboard/editors/media-editor';
import WebsiteEditor from '@/components/dashboard/editors/website-editor';
Expand All @@ -28,7 +29,7 @@ export default function EditEntry({ entry }) {
const [ category, setCategory ] = useState(entry.category);
const [ sources, setSources ] = useState(entry.sources);
const [ disclaimer, setDisclaimer ] = useState(entry.disclaimer);
const [ pronunciation, setPronunciation ] = useState(entry.pronunciation);
const [ pronunciations, setPronunciations ] = useState(entry.pronunciations);
const [ color, setColor ] = useState(entry.color);
const [ published, setPublished ] = useState(entry.published);
const [ greetings, setGreetings ] = useState(entry.greetings);
Expand Down Expand Up @@ -70,7 +71,7 @@ export default function EditEntry({ entry }) {
sources : sources,
disclaimer : disclaimer,
color : color,
pronunciation : pronunciation,
pronunciations : pronunciations,
published : published,
websites : websites,
greetings : greetings,
Expand All @@ -91,7 +92,7 @@ export default function EditEntry({ entry }) {
setCategory(results.entry.category)
setDisclaimer(results.entry.disclaimer)
setSources(results.entry.sources)
setPronunciation(results.entry.pronunciation)
setPronunciations(results.entry.pronunciations)
setColor(results.entry.color)
setPublished(results.entry.published)
setGreetings(results.entry.greetings)
Expand Down Expand Up @@ -181,12 +182,10 @@ export default function EditEntry({ entry }) {
</div>
: false}

{allowedColumns.indexOf('all') > -1 || allowedColumns.indexOf('pronunciation') > -1 ?
{allowedColumns.indexOf('all') > -1 || allowedColumns.indexOf('pronunciations') > -1 ?
<div className="mt-2.5">
<label className="text-gray-800 text-sm mb-1 block">{t('pronunciation')}</label>
<div className="relative flex items-center">
<input value={pronunciation ? pronunciation : ""} onChange={(e) => setPronunciation(e.target.value)} name="pronunciation" type="text" className="w-full text-gray-800 text-sm border border-gray-300 px-4 py-3 rounded-md outline-blue-600" placeholder={t('pronunciation-placeholder')} />
</div>
<label className="text-gray-800 text-sm mb-1 block">{t('pronunciations')}</label>
<PronunciationsEditor pronunciations={pronunciations} setPronunciations={setPronunciations} />
</div>
: false}

Expand Down
79 changes: 79 additions & 0 deletions src/components/dashboard/editors/pronunciations-editor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client'
import FileUploader from '@/components/dashboard/utils/file-uploader';
import { toast } from 'react-toastify';
import { useTranslations } from '@/i18n/client-i18n';

export default function PronunciationsEditor({ pronunciations, setPronunciations }) {

const t = useTranslations('Dashboard');
const tMaps = useTranslations('Maps');

const deleteObject = async (e, url) => {
e.preventDefault();
if(url) {
const key = url.split('/')[url.split('/').length - 1];

const response = await fetch(`/api/upload?key=${key}`, {
method : "DELETE",
headers : { 'Content-Type': 'application/json' }
}).then(resp => resp.json());

if(response.error) {
toast(response.error);
} else {
const newPronunciations = JSON.parse(JSON.stringify(pronunciations))
newPronunciations.newPronunciations(newPronunciations.findIndex(pronunciation => pronunciation.url === url), 1);
setPronunciations(newGreetings);
}
}
}

const changePronunciation = (value, action, prop, index) => {
const newPronunciations = [...pronunciations];
if(action === 'edit') {
newPronunciations[index][prop] = value;
}
setPronunciations(newPronunciations)
}

const afterUpload = (url) => {
const newPronunciations = JSON.parse(JSON.stringify(pronunciations))
newPronunciations.push({ url : url, text : '' })
setPronunciations(newPronunciations);
}

return (
<>
<div className="my-2.5">
{pronunciations.map((pronunciation, i) => {
return (
<div key={`pronunciation-${i}`} className="flex">
<a href={`${pronunciation.url}`} target="_blank">
<div className="w-40 h-20 relative flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="currentColor" className="bi bi-play-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445"/>
</svg>
<div className="absolute top-0 right-0" onClick={(e) => deleteObject(e, pronunciation.url)}>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" className="bi bi-trash3-fill m-1 p-1 bg-white rounded cursor-pointer hover:bg-gray-200" viewBox="0 0 16 16">
<path d="M11 1.5v1h3.5a.5.5 0 0 1 0 1h-.538l-.853 10.66A2 2 0 0 1 11.115 16h-6.23a2 2 0 0 1-1.994-1.84L2.038 3.5H1.5a.5.5 0 0 1 0-1H5v-1A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5m-5 0v1h4v-1a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5M4.5 5.029l.5 8.5a.5.5 0 1 0 .998-.06l-.5-8.5a.5.5 0 1 0-.998.06m6.53-.528a.5.5 0 0 0-.528.47l-.5 8.5a.5.5 0 0 0 .998.058l.5-8.5a.5.5 0 0 0-.47-.528M8 4.5a.5.5 0 0 0-.5.5v8.5a.5.5 0 0 0 1 0V5a.5.5 0 0 0-.5-.5"/>
</svg>
</div>
</div>
</a>
<div className="ml-5 w-full">
<div className="mb-2.5">
<div>
<label className="text-sm mb-2.5">{tMaps('text')}</label>
<input value={pronunciation.text} onChange={(e) => changePronunciation(e.target.value, 'edit', 'text', i)} type="text" className="w-full text-gray-800 text-sm border border-gray-300 px-4 py-3 rounded-md outline-blue-600" placeholder={t('text-placeholder')} />
</div>
</div>
</div>
</div>
)
})}
</div>
<FileUploader afterUpload={afterUpload} allowedTypes={"audio/midi, audio/mpeg, audio/ogg, audio/wav, audio/webm"} />
</>
);
}
Loading

0 comments on commit b37133f

Please sign in to comment.