diff --git a/app/db/README.md b/app/db/README.md index 04c2954f..bea704dd 100644 --- a/app/db/README.md +++ b/app/db/README.md @@ -51,6 +51,7 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C - Test note search - remove old ionic project - setup CI/CD +- ignore reserved search words (the ones that are too big to fit in the index) ## Optionally for Future diff --git a/app/db/src/app/app.module.ts b/app/db/src/app/app.module.ts index e86017f6..978b3cd6 100644 --- a/app/db/src/app/app.module.ts +++ b/app/db/src/app/app.module.ts @@ -11,6 +11,8 @@ import { FirebaseConfig } from './constants'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AngularFireDatabaseModule } from '@angular/fire/database'; +import { DragDropModule } from '@angular/cdk/drag-drop'; + import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatButtonModule } from '@angular/material/button'; import { MatInputModule } from '@angular/material/input'; @@ -104,6 +106,7 @@ import { AddToPageModalComponent } from './components/add-to-page-modal/add-to-p AngularFireAuthModule, AngularFireDatabaseModule, + DragDropModule, MatSidenavModule, MatToolbarModule, MatIconModule, diff --git a/app/db/src/app/common/array-operations.spec.ts b/app/db/src/app/common/array-operations.spec.ts new file mode 100644 index 00000000..1bf52679 --- /dev/null +++ b/app/db/src/app/common/array-operations.spec.ts @@ -0,0 +1,39 @@ +import { moveItemUpOrDown, moveItem } from './array-operations'; +import { MoveDirection } from './move-direction'; + +describe('Array Movement', () => { + it('Should move an item up', () => { + const a1: number[] = [1, 2, 3, 4]; + const a2 = moveItemUpOrDown(a1, 2, MoveDirection.Up); + + expect(a2[0]).toBe(2); + }); + + it('Should not move an item up if at start of array', () => { + const a1: number[] = [1, 2, 3, 4]; + const a2 = moveItemUpOrDown(a1, 1, MoveDirection.Up); + + expect(a2).toBe(a1); + }); + + it('Should move an item down', () => { + const a1: number[] = [1, 2, 3, 4]; + const a2 = moveItemUpOrDown(a1, 2, MoveDirection.Down); + + expect(a2[2]).toBe(2); + }); + + it('Should not move an item down if at the end of a list', () => { + const a1: number[] = [1, 2, 3, 4]; + const a2 = moveItemUpOrDown(a1, 4, MoveDirection.Down); + + expect(a2).toBe(a1); + }); + + it('Should move an item from one index to another', () => { + const a1: number[] = [1, 2, 3, 4]; + const a2 = moveItem(a1, 1, 3); + + expect(a2[3]).toBe(2); + }); +}); diff --git a/app/db/src/app/common/array-operations.ts b/app/db/src/app/common/array-operations.ts new file mode 100644 index 00000000..0d56a007 --- /dev/null +++ b/app/db/src/app/common/array-operations.ts @@ -0,0 +1,34 @@ +import { MoveDirection } from './move-direction'; +import { moveItemInArray } from '@angular/cdk/drag-drop'; + +/** + * Moves an item up (1 index towards 0) or down (1 index away from 0) immutably, returning a new array as the value. + * @param items Array in which to move the item. + * @param item Item to move up or down + * @param direction MoveDirection to go. + */ +export function moveItemUpOrDown(items: readonly T[], item: T, direction: MoveDirection): readonly T[] { + const idx = items.indexOf(item); + + if ( + (idx === 0 && direction === MoveDirection.Up) || // can't go up if you're at the top + (idx === items.length - 1 && direction === MoveDirection.Down) // can't go down if you're at the bottom + ) { + // you can't go up or down. + return items; + } + + return moveItem(items, idx, direction === MoveDirection.Up ? idx - 1 : idx + 1); +} + +/** + * Moves an item one index in an array to another immutably, returning a new array as the value. + * @param items Array in which to move the item. + * @param fromIndex Starting index of the item. + * @param toIndex Index to which the item should be moved. + */ +export function moveItem(items: readonly T[], fromIndex: number, toIndex: number): readonly T[] { + const array = [...items]; + moveItemInArray(array, fromIndex, toIndex); // copy the array. + return array; +} diff --git a/app/db/src/app/components/saved-page-card/saved-page-card.component.html b/app/db/src/app/components/saved-page-card/saved-page-card.component.html index 690e2c26..3f39f8c9 100644 --- a/app/db/src/app/components/saved-page-card/saved-page-card.component.html +++ b/app/db/src/app/components/saved-page-card/saved-page-card.component.html @@ -5,14 +5,18 @@ Page: {{ savedPage.title }}
- - +
+
{{ format(q) }} - - +
+
diff --git a/app/db/src/app/components/saved-page-card/saved-page-card.component.scss b/app/db/src/app/components/saved-page-card/saved-page-card.component.scss index 7c1e8669..934bb005 100644 --- a/app/db/src/app/components/saved-page-card/saved-page-card.component.scss +++ b/app/db/src/app/components/saved-page-card/saved-page-card.component.scss @@ -13,3 +13,50 @@ font-size: var(--card-font-size); padding: 0.5rem; } + +.card-list { + width: 100%; + max-width: 100%; + min-height: 1rem; + display: block; + background: white; + border-radius: 4px; + overflow: hidden; +} + +.card-item { + font-family: var(--card-font-family); + font-size: var(--card-font-size); + border-bottom: 1px solid whitesmoke; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + cursor: move; + background: white; + padding-bottom: 3px; +} + +.card-item:last-child { + border: none; +} + +.card-list.cdk-drop-list-dragging .card-item:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} + +.cdk-drag-placeholder { + opacity: 0; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/app/db/src/app/components/saved-page-card/saved-page-card.component.ts b/app/db/src/app/components/saved-page-card/saved-page-card.component.ts index b889141c..e4127de7 100644 --- a/app/db/src/app/components/saved-page-card/saved-page-card.component.ts +++ b/app/db/src/app/components/saved-page-card/saved-page-card.component.ts @@ -1,4 +1,5 @@ -import { Component, ElementRef, ChangeDetectionStrategy, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { MatDialog } from '@angular/material/dialog'; import { AppService } from '../../services/app.service'; import { Observable } from 'rxjs'; @@ -79,7 +80,12 @@ export class SavedPageCardComponent implements OnInit { } }); } + + moveSavedPageCard(event: CdkDragDrop) { + this.appService.moveSavedPageCard(this.savedPage, event.previousIndex, event.currentIndex); + } + ngOnInit(): void { - console.log(this.savedPage); + // console.log(this.savedPage); } } diff --git a/app/db/src/app/services/app-state-actions.ts b/app/db/src/app/services/app-state-actions.ts index 4b35274f..d8fdfabf 100644 --- a/app/db/src/app/services/app-state-actions.ts +++ b/app/db/src/app/services/app-state-actions.ts @@ -41,6 +41,15 @@ export class AppActionFactory { } as AppAction; } + static newMoveSavedPageCard(savedPage: SavedPage, fromIndex: number, toIndex: number): AppAction { + return { + type: 'MOVE_SAVED_PAGE_CARD', + savedPage, + fromIndex, + toIndex, + } as AppAction; + } + static newRemoveSavedPage(savedPage: SavedPage): AppAction { return { type: 'REMOVE_SAVED_PAGE', @@ -197,6 +206,12 @@ export type AppAction = type: 'REMOVE_SAVED_PAGE'; savedPage: SavedPage; } + | { + type: 'MOVE_SAVED_PAGE_CARD'; + fromIndex: number; + toIndex: number; + savedPage: SavedPage; + } | { type: 'ADD_CARD_TO_SAVED_PAGE'; card: CardItem; diff --git a/app/db/src/app/services/app-state-reducer.spec.ts b/app/db/src/app/services/app-state-reducer.spec.ts index 76429268..390fc7cd 100644 --- a/app/db/src/app/services/app-state-reducer.spec.ts +++ b/app/db/src/app/services/app-state-reducer.spec.ts @@ -242,6 +242,14 @@ describe('AppService Reducer', () => { // 'SAVE_PAGE'; // 'GET_SAVED_PAGE'; + it('MOVE_SAVED_PAGE_CARD', () => { + const page = preState.savedPages.value[1]; + + const action1 = AppActionFactory.newMoveSavedPageCard(page, 1, 0); + const testState = reducer(preState, action1); + expect(testState.savedPages.value[1].queries[0].qry).toBe('G1', 'Failed to move card in saved page'); + }); + // 'ADD_CARD_TO_SAVED_PAGE'; it('ADD_CARD', () => { diff --git a/app/db/src/app/services/app-state-reducer.ts b/app/db/src/app/services/app-state-reducer.ts index 7e9239d6..631373ee 100644 --- a/app/db/src/app/services/app-state-reducer.ts +++ b/app/db/src/app/services/app-state-reducer.ts @@ -4,13 +4,13 @@ import { AppState, DisplaySettings, PageSettings } from '../models/app-state'; import { IStorable, Storable } from '../common/storable'; import { NoteItem } from '../models/note-state'; -import { MoveDirection } from '../common/move-direction'; import { mergeCardList } from '../common/card-operations'; -import { AppAction } from './app-state-actions'; +import { AppAction, AppActionFactory } from './app-state-actions'; import { initialState } from './app-state-initial-state'; import { SavedPage } from '../models/page-state'; import { CardType, CardItem } from '../models/card-state'; +import { moveItem, moveItemUpOrDown } from '../common/array-operations'; export function getNewestStorable(candidate: IStorable, incumbant: IStorable): IStorable { // if the candidate is null, then return the state. @@ -105,17 +105,35 @@ export function reducer(state: AppState, action: AppAction): AppState { autocomplete: [...action.words], }; } + + //#region Saved Pages + case 'UPDATE_SAVED_PAGES': { - const item = getNewestStorable(action.savedPages, state.savedPages); + const savedPages = getNewestStorable(action.savedPages, state.savedPages); + + // only true if a currentSavedPage was set, indicating that the user + // is currently looking at a saved page. + const hasCurrentSavedPage = + state.currentSavedPage !== null && + state.currentSavedPage !== undefined && + action.savedPages.value.some((o) => o.id === state.currentSavedPage.id); + + const currentSavedPage = hasCurrentSavedPage + ? action.savedPages.value.find((o) => o.id === state.currentSavedPage.id) + : null; return { ...state, + // if the currentSavedPage was loaded, replace it with the info from the + // new savedPages array, as it might have changed. + cards: hasCurrentSavedPage ? currentSavedPage.queries : state.cards, + currentSavedPage: hasCurrentSavedPage ? currentSavedPage : state.currentSavedPage, savedPagesLoaded: true, - savedPages: item, + savedPages, // update the savedPages }; } case 'UPDATE_SAVED_PAGE': { - const savedPages = new Storable( + const newSavedPages = new Storable( state.savedPages.value.map((o) => { if (o.id === action.savedPage.id) { return action.savedPage; @@ -124,12 +142,8 @@ export function reducer(state: AppState, action: AppAction): AppState { }) ); - const item = getNewestStorable(savedPages, state.savedPages); - return { - ...state, - savedPagesLoaded: true, - savedPages: item, - }; + const savedPages = getNewestStorable(newSavedPages, state.savedPages); + return reducer(state, AppActionFactory.newUpdateSavedPages(savedPages)); } case 'REMOVE_SAVED_PAGE': { const savedPages = new Storable(state.savedPages.value.filter((o) => o.id !== action.savedPage.id)); @@ -190,6 +204,15 @@ export function reducer(state: AppState, action: AppAction): AppState { cards: [...page.queries], }; } + case 'MOVE_SAVED_PAGE_CARD': { + const queries = moveItem(action.savedPage.queries, action.fromIndex, action.toIndex); + const savedPage = { + ...action.savedPage, + queries, // update the queries. + }; + + return reducer(state, AppActionFactory.newUpdateSavedPage(savedPage)); + } case 'ADD_CARD_TO_SAVED_PAGE': { const savedPages = new Storable([ ...state.savedPages.value.map((o) => { @@ -214,6 +237,9 @@ export function reducer(state: AppState, action: AppAction): AppState { savedPages, }); } + + //#endregion + case 'ADD_CARD': { let cards = []; @@ -259,25 +285,7 @@ export function reducer(state: AppState, action: AppAction): AppState { }; } case 'MOVE_CARD': { - let cards = []; - const idx = state.cards.indexOf(action.card); - - if ( - (idx === 0 && action.direction === MoveDirection.Up) || // can't go up if you're at the top - (idx === state.cards.length - 1 && action.direction === MoveDirection.Down) // can't go down if you're at the bottom - ) { - // you can't go up. - return state; - } - - const before = state.cards.slice(0, idx); - const after = state.cards.slice(idx + 1); - - if (action.direction === MoveDirection.Down) { - cards = [...before, after[0], action.card, ...after.slice(1)]; - } else { - cards = [...before.slice(0, before.length - 1), action.card, before[before.length - 1], ...after]; - } + const cards = moveItemUpOrDown(state.cards, action.card, action.direction); return { ...state, diff --git a/app/db/src/app/services/app.service.ts b/app/db/src/app/services/app.service.ts index 1e660e26..c34ecd0a 100644 --- a/app/db/src/app/services/app.service.ts +++ b/app/db/src/app/services/app.service.ts @@ -30,6 +30,7 @@ import { reducer } from './app-state-reducer'; import { initialState } from './app-state-initial-state'; import { CardItem, CardType } from '../models/card-state'; import { SavedPage } from '../models/page-state'; +import { AppActionFactory } from './app-state-actions'; @Injectable({ providedIn: 'root', @@ -118,18 +119,15 @@ export class AppService extends createStateService(reducer, initialState) { }); } removeSavedPage(savedPage: SavedPage) { - this.dispatch({ - type: 'REMOVE_SAVED_PAGE', - savedPage, - }); + this.dispatch(AppActionFactory.newRemoveSavedPage(savedPage)); + } + + moveSavedPageCard(savedPage: SavedPage, fromIndex: number, toIndex: number) { + this.dispatch(AppActionFactory.newMoveSavedPageCard(savedPage, fromIndex, toIndex)); } addCardToSavedPage(pageId: string, card: CardItem) { - this.dispatch({ - type: 'ADD_CARD_TO_SAVED_PAGE', - card, - pageId, - }); + this.dispatch(AppActionFactory.newAddCardToSavedPage(card, pageId)); } //#endregion