diff --git a/config/index.ts b/config/index.ts index 2951990..58fcde8 100644 --- a/config/index.ts +++ b/config/index.ts @@ -6,10 +6,9 @@ import { BrowserActionProvider, } from '@exteranto/api' -import { - ContextMenusProvider, -} from '@/services/contextMenus' +import { ContextMenusProvider } from '@/services/contextMenus' +import { Awi } from 'awi' import { env } from '@/helpers' export default { @@ -62,5 +61,13 @@ export default { savePage: { id: 'save-page-to-ideablob', title: 'Save page to Ideablob' }, }, + awi: { + base: () => new Awi() + // This would be so nice. + // .use(async req => req.executor = Container.resolveOptional(AbstractExecutor)) + .use(async req => req.base = 'https://server-api.com/prod/v1'), + }, + }, + } diff --git a/package-lock.json b/package-lock.json index 5541f80..7d9e1af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@bausano/data-structures": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@bausano/data-structures/-/data-structures-1.0.0.tgz", - "integrity": "sha512-QOcTUUNen5IQMvdrvGoT31cdY3052+dCoOxLx8rw4sSDOSkYgzW1HBMJ9oaEZsbBhUcAf8+MMrCVhVEUFsUoNA==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bausano/data-structures/-/data-structures-1.0.1.tgz", + "integrity": "sha512-3PD9U5oaaXgXUjr9Q8R/Mt9tq/UF4T0Ck1/5tTAyk1BlrknagID949JzOtIO2E+qRfxL07tGSlVk0duD/Jrp+Q==" }, "@exteranto/api": { "version": "2.5.0", @@ -162,9 +162,9 @@ "dev": true }, "@types/node": { - "version": "10.12.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.27.tgz", - "integrity": "sha512-e9wgeY6gaY21on3ve0xAjgBVjGDWq/xUteK0ujsE53bUoxycMkqfnkUgMt6ffZtykZ5X12Mg3T7Pw4TRCObDKg==", + "version": "10.12.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.29.tgz", + "integrity": "sha512-J/tnbnj8HcsBgCe2apZbdUpQ7hs4d7oZNTYA5bekWdP0sr2NGsOpI/HRdDroEi209tEvTcTtxhD0FfED3DhEcw==", "dev": true }, "@types/sinon": { @@ -568,11 +568,11 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "awi": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/awi/-/awi-0.0.5.tgz", - "integrity": "sha512-voW4wWTxseQ6LZZITVonTtZJlaL8sTttrDZCnrwt8LWhzshW9ni71EfXbNU6np4Hsdx6hWoAnjfeepzS/FLixQ==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/awi/-/awi-0.0.6.tgz", + "integrity": "sha512-Cb6cUa0yx9LysUpJpFTyqJh9ubG2KskEct7/ZmpBnUCTYnTf8GmX3xpxBzrehq+8IIlPa3zrxhc+wTe5vWmSKA==", "requires": { - "@bausano/data-structures": "^1.0.0" + "@bausano/data-structures": "^1.0.1" } }, "aws-sign2": { @@ -4127,15 +4127,6 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, - "pre-push": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pre-push/-/pre-push-0.1.1.tgz", - "integrity": "sha1-Kip5gn0kOnbJEImJescH9F5xaqw=", - "dev": true, - "requires": { - "shelljs": "0.3.x" - } - }, "prettier": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.3.tgz", @@ -4643,12 +4634,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", - "dev": true - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", diff --git a/package.json b/package.json index 8db9f53..e1c44eb 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ "@exteranto/core": "^2.5.0", "@exteranto/exceptions": "^2.5.0", "@exteranto/utils": "^2.5.0", - "awi": "^0.0.5" + "awi": "^0.0.6" }, "devDependencies": { "@types/chai": "^4.1.4", "@types/chai-as-promised": "^7.1.0", "@types/mocha": "^5.2.6", - "@types/node": "^10.12.27", + "@types/node": "^10.12.29", "@types/sinon": "^5.0.1", "bulma": "^0.7.4", "chai": "^4.1.2", diff --git a/src/app/background/events.ts b/src/app/background/events.ts index 1146677..f3103c8 100644 --- a/src/app/background/events.ts +++ b/src/app/background/events.ts @@ -21,6 +21,7 @@ import { Debug } from '@/helpers' import { Handler } from '@/exceptions/Handler' import { + SharesPage, ClearsLocalStorage, BootsMessageListener, CustomizesContextMenus, @@ -33,6 +34,7 @@ export default (touch: (event: any) => ListenerBag) => { .addListener(new CustomizesContextMenus) touch(SavePageMenuItemClickedEvent) + .addListener(new SharesPage) /** * Extension lifecycle events. diff --git a/src/app/background/listeners/SharesPage.ts b/src/app/background/listeners/SharesPage.ts new file mode 100644 index 0000000..1869c2b --- /dev/null +++ b/src/app/background/listeners/SharesPage.ts @@ -0,0 +1,22 @@ +import { Listener } from '@exteranto/core' +import { Blob, Blobs } from '@/resources/Blobs' +import { ResourceManager } from '@/services/resourceManager' +import { Manager } from '@/services/resourceManager/annotations' +import { SavePageMenuItemClickedEvent } from '../events/SavePageMenuItemClickedEvent' + +export class SharesPage implements Listener { + + /** + * The context menus API service. + */ + @Manager(Blobs) + private manager: ResourceManager + + /** + * Handle the fired event. + */ + public handle (event: SavePageMenuItemClickedEvent) : void { + this.manager.store({ url: event.page }) + } + +} diff --git a/src/app/background/listeners/index.ts b/src/app/background/listeners/index.ts index 7efa2c2..942a737 100644 --- a/src/app/background/listeners/index.ts +++ b/src/app/background/listeners/index.ts @@ -1,4 +1,5 @@ +export * from './SharesPage' export * from './ClearsLocalStorage' export * from './BootsMessageListener' export * from './CustomizesContextMenus' diff --git a/src/resources/Blobs.ts b/src/resources/Blobs.ts new file mode 100644 index 0000000..f14a78c --- /dev/null +++ b/src/resources/Blobs.ts @@ -0,0 +1,21 @@ +import { Resource } from '@/services/resourceManager' + +export interface Blob { + + /** + * The blob url. + */ + url: string + +} + +export class Blobs extends Resource implements Blob { + + /** + * {@inheritdoc} + */ + get url () : string { + return this.data.url + } + +} diff --git a/src/services/contextMenus/ContextMenus.ts b/src/services/contextMenus/ContextMenus.ts index 54f7f41..01a73bc 100644 --- a/src/services/contextMenus/ContextMenus.ts +++ b/src/services/contextMenus/ContextMenus.ts @@ -1,3 +1,4 @@ +import { Class } from '@/types' import { ContextMenuItemClickedEvent } from './events' import { Autowired, Dispatcher } from '@exteranto/core' import { ContextMenuItemCreatinonFailedException } from './exceptions' diff --git a/src/services/contextMenus/types.ts b/src/services/contextMenus/types.ts index 8939004..19020d9 100644 --- a/src/services/contextMenus/types.ts +++ b/src/services/contextMenus/types.ts @@ -1,3 +1,4 @@ +import { Class } from '@/types' import { ItemType } from './ItemType' import { ContextType } from './ContextType' import { ContextMenuItemClickedEvent } from './events' diff --git a/src/services/resourceManager/Paginator.ts b/src/services/resourceManager/Paginator.ts new file mode 100644 index 0000000..7c9143d --- /dev/null +++ b/src/services/resourceManager/Paginator.ts @@ -0,0 +1,41 @@ +import { Client } from 'awi' +import { Class } from '@/types' +import { Param } from '@exteranto/core' + +export class Paginator { + + /** + * Awi instance configurations. + */ + @Param('awi') + private awi: { [key: string]: () => Client } + + /** + * The curren page for the paginator. + */ + private page: number = 0 + + /** + * + * @param resource The resource to paginate over + * @param perpage The count of items to show per page + */ + constructor (private resource: Class, private perpage: number) { + // + } + + /** + * A method to retrieve the next page of the resource. + * TODO: total etc + * + * @return The next page of the resource instances + */ + public async next () : Promise { + return this.awi.base() + .use(async req => req.query.skip = String(this.page * this.perpage)) + .use(async req => req.query.take = String(this.perpage)) + .body(this.resource.name.toLowerCase()) + .then(resources => resources.map(item => new this.resource(item))) + } + +} diff --git a/src/services/resourceManager/Resource.ts b/src/services/resourceManager/Resource.ts new file mode 100644 index 0000000..2ac460a --- /dev/null +++ b/src/services/resourceManager/Resource.ts @@ -0,0 +1,8 @@ + +export class Resource { + + constructor (protected data: T) { + // + } + +} diff --git a/src/services/resourceManager/ResourceManager.ts b/src/services/resourceManager/ResourceManager.ts new file mode 100644 index 0000000..61db6c6 --- /dev/null +++ b/src/services/resourceManager/ResourceManager.ts @@ -0,0 +1,63 @@ +import { Class } from '@/types' +import { Client, Response } from 'awi' +import { Paginator } from './Paginator' +import { Param } from '@exteranto/core' + +export class ResourceManager { + + /** + * Awi instance configurations. + */ + @Param('awi') + private awi: { [key: string]: () => Client } + + /** + * @param resource The resource to use for this resource manager instance + */ + constructor (private resource: Class) { + // + } + + /** + * List the resource. + * + * @return An array of the resource instances + */ + public async list () : Promise { + return this.awi.base() + .body(this.resource.name.toLowerCase()) + .then(resources => resources.map(item => new this.resource(item))) + } + + /** + * Store the provided resource. + * + * @param storable The resource to be stored + * @return The response from the server + */ + public async store (storable: T) : Promise { + return this.awi.base() + .post(this.resource.name.toLowerCase(), storable) + } + + /** + * Retrieve the paginator for the current resource. + * + * @param perpage The count of items per page + * @return The paginator instance linked to the resource + */ + public paginator (perpage: number) : Paginator { + return new Paginator(this.resource, perpage) + } + + /** + * Static constructor for the resource manager. + * + * @param resource The resource to use for the new resource manager instance + * @return A new resource manager instance + */ + public static for (resource: Class) { + return new ResourceManager(resource) + } + +} diff --git a/src/services/resourceManager/ResourceManagerProvider.ts b/src/services/resourceManager/ResourceManagerProvider.ts new file mode 100644 index 0000000..f564e9f --- /dev/null +++ b/src/services/resourceManager/ResourceManagerProvider.ts @@ -0,0 +1,26 @@ +import { Provider, Script } from '@exteranto/core' + +export class ResourceManagerProvider extends Provider { + + /** + * {@inheritdoc} + */ + public only () : Script[] { + return [Script.BACKGROUND] + } + + /** + * {@inheritdoc} + */ + public boot () : void { + // + } + + /** + * {@inheritdoc} + */ + public register () : void { + // + } + +} diff --git a/src/services/resourceManager/annotations.ts b/src/services/resourceManager/annotations.ts new file mode 100644 index 0000000..c7ba7cd --- /dev/null +++ b/src/services/resourceManager/annotations.ts @@ -0,0 +1,26 @@ +import { Class } from '@/types' +import { ResourceManager } from './ResourceManager' + +/** + * The @Manager annotation that resolves the resource manager for the provided + * resource class. + * + * @param resource The resource to create the manager for. + */ +export function Manager (resource: Class) : (target: any, property: string) => void { + return (target: any, property: string) : void => { + const key: string = `__manager_${resource.name}` + + Object.defineProperty(target, property, { + get () : ResourceManager { + return target[key] === undefined + ? target[key] = ResourceManager.for(resource) + : target[key] + }, + + set (value: ResourceManager) : void { + target[key] = value + }, + }) + } +} diff --git a/src/services/resourceManager/index.ts b/src/services/resourceManager/index.ts new file mode 100644 index 0000000..0ca607f --- /dev/null +++ b/src/services/resourceManager/index.ts @@ -0,0 +1,14 @@ + +/** + * Export package annotations. + */ + +export * from './annotations' + +/** + * Export package services. + */ + +export { Resource } from './Resource' +export { Paginator } from './Paginator' +export { ResourceManager } from './ResourceManager' diff --git a/src/types.d.ts b/src/types.d.ts index 3b469ce..416f451 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -4,8 +4,8 @@ * Custom constructor types. */ -declare type Abstract = Function & { prototype: T } +export type Abstract = Function & { prototype: T } -declare interface Class extends Abstract { +export interface Class extends Abstract { new (...args: any[]) : T } diff --git a/test/setup.ts b/test/setup.ts index ff8c8bd..7c1a749 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -11,20 +11,5 @@ import * as browser from 'sinon-chrome/extensions' import * as chai from "chai" import * as chaiAsPromised from "chai-as-promised" -chai.use(chaiAsPromised) - -require('jsdom-global')() -require('vue').config.productionTip = false - -/** - * Bootstraping exteranto. - */ -import { App, Script } from '@exteranto/core' -import config from '../config' -import events from '../src/app/background/events' - -const app: App = new App(Script.BACKGROUND, config, events) - -app.start() -app.boot() +chai.use(chaiAsPromised) diff --git a/test/specs/App.spec.ts b/test/specs/App.spec.ts deleted file mode 100644 index 43ae043..0000000 --- a/test/specs/App.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { expect } from 'chai' - -describe('Tests', () => { - it('should work', () => { - expect(1).to.equal(2 - 1) - }) -}) diff --git a/test/specs/ResourceManager.spec.ts b/test/specs/ResourceManager.spec.ts new file mode 100644 index 0000000..9472406 --- /dev/null +++ b/test/specs/ResourceManager.spec.ts @@ -0,0 +1,59 @@ +import { expect } from 'chai' + +// TODO: Export +import { Awi } from 'awi' +import { Resource } from '../../src/services/resourceManager/Resource' +import { Manager } from '../../src/services/resourceManager/annotations' +import { ResourceManager } from '../../src/services/resourceManager/ResourceManager' + +describe('Resource Manager', () => { + + it('can be assigned via an annotation', () => { + expect(new NeedsManager().manager).to.be.an.instanceOf(ResourceManager) + }) + + it('loads a resource', async () => { + // Given. + const manager: ResourceManager = new ResourceManager(Todos) + + ;(manager as any).awi = { + base: () => new Awi() + .use(async req => req.base = 'https://jsonplaceholder.typicode.com') + } + + // When. + const todos: Todo[] = await manager.list() + + // Then. + expect(todos.shift()) + .to.have.property('title').that.is.a('string') + expect(todos.shift()) + .to.have.property('completed').that.is.a('boolean') + + }) + +}) + +interface Todo { + title: string, + completed: boolean, +} + +class Todos extends Resource implements Todo { + + get title () : string { + return this.data.title + } + + get completed () : boolean { + return this.data.completed + } + +} + +class NeedsManager { + + @Manager(Todos) + public manager: ResourceManager + +}