diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c389487..686311d9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ - `httpConfAddMethodAngular` was removed - The members of the `ProgressReport` type returned by `calcProgress()` have been renamed from `{struct, stats, total_results}` to `{data, percent, hits}` +### Fixed + +- The response JSON download button now handles POST and logged-in requests, and has been moved into each corresponding result tab [#417](https://github.com/spraakbanken/korp-frontend/issues/417) + ## [9.7.2] - 2024-12-09 ### Added diff --git a/app/scripts/backend/lemgram-proxy.ts b/app/scripts/backend/lemgram-proxy.ts index b4e257f66..23fb3ff16 100644 --- a/app/scripts/backend/lemgram-proxy.ts +++ b/app/scripts/backend/lemgram-proxy.ts @@ -8,7 +8,6 @@ import { RelationsParams, RelationsResponse } from "./types/relations" export class LemgramProxy extends BaseProxy<"relations"> { prevParams?: RelationsParams - prevUrl?: string makeRequest( word: string, @@ -41,7 +40,6 @@ export class LemgramProxy extends BaseProxy<"relations"> { beforeSend(req, settings) { self.addAuthorizationHeader(req) - self.prevUrl = settings.url }, } satisfies AjaxSettings diff --git a/app/scripts/backend/stats-proxy.ts b/app/scripts/backend/stats-proxy.ts index 992f2c260..42f5424b6 100644 --- a/app/scripts/backend/stats-proxy.ts +++ b/app/scripts/backend/stats-proxy.ts @@ -39,7 +39,6 @@ export function normalizeStatsData(data: CountResponse): StatsNormalized { export class StatsProxy extends BaseProxy<"count"> { prevParams: CountParams | null - prevUrl?: string constructor() { super() @@ -128,7 +127,6 @@ export class StatsProxy extends BaseProxy<"count"> { data, beforeSend(req, settings) { self.addAuthorizationHeader(req) - self.prevUrl = settings.url }, error(jqXHR, textStatus, errorThrown) { diff --git a/app/scripts/components/json_button.ts b/app/scripts/components/json_button.ts new file mode 100644 index 000000000..539052191 --- /dev/null +++ b/app/scripts/components/json_button.ts @@ -0,0 +1,48 @@ +/** @format */ +import angular, { IController, IScope } from "angular" +import { downloadFile, html } from "@/util" +import { API } from "@/backend/types" +import { korpRequest } from "@/backend/common" + +type JsonButtonController = IController & { + endpoint: keyof API + params: API[keyof API]["params"] +} + +type JsonButtonScope = IScope & { + loading: boolean + openJson: () => Promise +} + +angular.module("korpApp").component("jsonButton", { + template: html`
+ + +
`, + bindings: { + endpoint: "<", + params: "<", + }, + controller: [ + "$scope", + function ($scope: JsonButtonScope) { + const $ctrl = this as JsonButtonController + $scope.loading = false + + $scope.openJson = async function () { + // $apply needed becase async + $scope.$applyAsync(($scope: JsonButtonScope) => ($scope.loading = true)) + const data = await korpRequest($ctrl.endpoint, $ctrl.params) + const json = JSON.stringify(data, null, 2) + downloadFile(json, `korp-${$ctrl.endpoint}.json`, "application/json") + $scope.$applyAsync(($scope: JsonButtonScope) => ($scope.loading = false)) + } + }, + ], +}) diff --git a/app/scripts/components/results.ts b/app/scripts/components/results.ts index ce1c80101..8ed0b2c71 100644 --- a/app/scripts/components/results.ts +++ b/app/scripts/components/results.ts @@ -1,6 +1,7 @@ /** @format */ import angular, { IController } from "angular" import { html } from "@/util" +import "@/components/json_button" import "@/components/korp-error" import "@/components/kwic" import "@/components/loglike-meter" @@ -63,16 +64,11 @@ angular.module("korpApp").component("results", { prev-url="proxy.prevUrl" corpus-order="corpusOrder" > + - + {{'statistics' | loc:$root.lang}} + - + {{'word_picture' | loc:$root.lang}} + @@ -280,10 +280,6 @@ angular.module("korpApp").component("results", { - - - - & IRepeatScope & TabHashScope type ExampleCtrlScope = ScopeBase & { - $parent: { $parent: TabHashScope } closeTab: (idx: number, e: Event) => void hitsPictureData?: any hitspictureClick?: (page: number) => void @@ -54,8 +53,6 @@ class ExampleCtrl extends KwicCtrl { s.corpusHits = undefined s.aborted = false - s.tabindex = s.$parent.$parent.tabset.tabs.length - 1 + s.$index - s.newDynamicTab() s.isReadingMode = () => { @@ -156,10 +153,6 @@ class ExampleCtrl extends KwicCtrl { return def } - s.isActive = () => { - return s.tabindex == s.activeTab - } - if (s.kwicTab.queryParams) { s.makeRequest().then(() => { // s.onentry() diff --git a/app/scripts/controllers/kwic_controller.ts b/app/scripts/controllers/kwic_controller.ts index 770b29b99..77c92ad31 100644 --- a/app/scripts/controllers/kwic_controller.ts +++ b/app/scripts/controllers/kwic_controller.ts @@ -30,7 +30,6 @@ export type KwicCtrlScope = TabHashScope & { hitsPerPage?: `${number}` | number ignoreAbort?: boolean initialSearch?: boolean - isActive: () => boolean isReadingMode: () => boolean kwic?: ApiKwic[] loading?: boolean @@ -47,7 +46,6 @@ export type KwicCtrlScope = TabHashScope & { readingChange: () => void renderCompleteResult: (data: Response, isPaging?: boolean) => void renderResult: (data: Response) => void - tabindex?: number toggleReading: () => void } @@ -105,8 +103,6 @@ export class KwicCtrl implements IController { s.proxy = kwicProxyFactory.create() - s.tabindex = 0 - this.initPage() s.pageChange = function (page) { @@ -275,28 +271,18 @@ export class KwicCtrl implements IController { data.kwic = [] } - if (s.isActive()) { - $rootScope.jsonUrl = s.proxy.prevUrl - } - s.corpusOrder = data.corpus_order s.kwic = data.kwic } s.onentry = () => { - $rootScope.jsonUrl = s.proxy.prevUrl s.active = true } s.onexit = () => { - $rootScope.jsonUrl = undefined s.active = false } - s.isActive = () => { - return s.tabindex == s.activeTab - } - s.countCorpora = () => { return s.proxy.prevParams?.corpus ? s.proxy.prevParams.corpus.split(",").length : null } diff --git a/app/scripts/controllers/statistics_controller.ts b/app/scripts/controllers/statistics_controller.ts index 2d206ab0c..5a046469f 100644 --- a/app/scripts/controllers/statistics_controller.ts +++ b/app/scripts/controllers/statistics_controller.ts @@ -28,11 +28,8 @@ type StatsResultCtrlScope = TabHashScope & { proxy: StatsProxy searchParams: SearchParams showStatistics: boolean - tabindex: number - isActive: () => boolean makeRequest: (cqp: string) => void onentry: () => void - onexit: () => void onProgress: (progressObj: ProgressReport<"count">, isPaging?: boolean) => void renderResult: (columns: SlickgridColumn[], data: Dataset) => void resetView: () => void @@ -59,8 +56,6 @@ angular.module("korpApp").directive("statsResultCtrl", () => ({ s.error = false s.progress = 0 - s.tabindex = 2 - s.proxy = statsProxyFactory.create() $rootScope.$on("make_request", (event, cqp: string) => { @@ -72,21 +67,12 @@ angular.module("korpApp").directive("statsResultCtrl", () => ({ }) s.onentry = () => { - s.$root.jsonUrl = s.proxy.prevUrl // workaround for bug in slickgrid // slickgrid should add this automatically, but doesn't $("#myGrid").css("position", "relative") $(window).trigger("resize") } - s.onexit = () => { - s.$root.jsonUrl = undefined - } - - s.isActive = () => { - return s.tabindex == s.activeTab - } - s.resetView = () => { $("myGrid").empty() $("#exportStatsSection").show() @@ -163,10 +149,6 @@ angular.module("korpApp").directive("statsResultCtrl", () => ({ } s.renderResult = (columns, data) => { - if (s.isActive()) { - s.$root.jsonUrl = s.proxy.prevUrl - } - s.columns = columns if (data[0].total[0] === 0) { diff --git a/app/scripts/controllers/word_picture_controller.ts b/app/scripts/controllers/word_picture_controller.ts index a2f5bcc67..3674d27b6 100644 --- a/app/scripts/controllers/word_picture_controller.ts +++ b/app/scripts/controllers/word_picture_controller.ts @@ -22,12 +22,9 @@ type WordpicCtrlScope = TabHashScope & { hasData: boolean hitSettings: `${number}`[] ignoreAbort: boolean - isActive: () => boolean loading: boolean makeRequest: () => void noHits: boolean - onentry: () => void - onexit: () => void onProgress: (progressObj: ProgressReport<"relations">) => void progress: number proxy: LemgramProxy @@ -38,7 +35,6 @@ type WordpicCtrlScope = TabHashScope & { settings: { showNumberOfHits: `${number}` } - tabindex: number wordPic: boolean } @@ -70,7 +66,6 @@ angular.module("korpApp").directive("wordpicCtrl", () => ({ "$timeout", ($scope: WordpicCtrlScope, $rootScope: RootScope, $location: LocationService, $timeout: ITimeoutService) => { const s = $scope - s.tabindex = 3 s.proxy = lemgramProxyFactory.create() s.error = false @@ -150,20 +145,6 @@ angular.module("korpApp").directive("wordpicCtrl", () => ({ }) } - s.onentry = () => { - if (s.hasData) { - s.$root.jsonUrl = s.proxy.prevUrl - } - } - - s.onexit = () => { - s.$root.jsonUrl = undefined - } - - s.isActive = () => { - return s.tabindex == s.activeTab - } - s.renderResult = (data, query) => { s.loading = false s.progress = 100 @@ -173,10 +154,6 @@ angular.module("korpApp").directive("wordpicCtrl", () => ({ return } - if (s.isActive()) { - s.$root.jsonUrl = s.proxy.prevUrl - } - s.hasData = true if (!data.relations) { s.noHits = true diff --git a/app/scripts/root-scope.types.ts b/app/scripts/root-scope.types.ts index 9f879e383..050e52c9e 100644 --- a/app/scripts/root-scope.types.ts +++ b/app/scripts/root-scope.types.ts @@ -27,7 +27,6 @@ export type RootScope = IRootScopeService & { mapTabs: MapTab[] textTabs: TextTab[] waitForLogin: boolean - jsonUrl?: string lang: string loc_data: LangLocMap openErrorModal: (options: { diff --git a/app/scripts/util.ts b/app/scripts/util.ts index 5ce531d87..f9ad7e9c1 100644 --- a/app/scripts/util.ts +++ b/app/scripts/util.ts @@ -472,6 +472,17 @@ export function buildUrl(base: string, params: Record): string { return url.toString() } +/** Trigger a download in the browser. */ +export function downloadFile(data: string, filename: string, type: string) { + const blob = new Blob([data], { type }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = filename + a.click() + URL.revokeObjectURL(url) +} + /** * Sort elements alphabetically by a given attribute. * @param elems A list of objects. diff --git a/app/styles/styles.scss b/app/styles/styles.scss index 3516aaf7b..ab19a6aec 100644 --- a/app/styles/styles.scss +++ b/app/styles/styles.scss @@ -703,16 +703,6 @@ table { background-color: #FFDDD8; } -#json-link { - float: right; - // Clear floats so that the JSON link is right-justified and not - // placed to the left of the download links. - clear: both; - img { - border: 0px; - } -} - #download-links { float: right; } diff --git a/app/translations/locale-eng.json b/app/translations/locale-eng.json index 2292180f7..b7dfdab24 100644 --- a/app/translations/locale-eng.json +++ b/app/translations/locale-eng.json @@ -79,6 +79,7 @@ "show_deptree": "Show Dependency Tree", "dep_tree": "Dependency Tree", "download": "Download as", + "download_response": "Save API response (JSON)", "toggle_select": "select", "is": "is", diff --git a/app/translations/locale-swe.json b/app/translations/locale-swe.json index 2bd8f1b22..1e3ab0c28 100644 --- a/app/translations/locale-swe.json +++ b/app/translations/locale-swe.json @@ -79,6 +79,7 @@ "show_deptree": "Visa dependensträd", "dep_tree": "Dependensträd", "download": "Ladda ner som", + "download_response": "Spara API-svar (JSON)", "toggle_select": "välj", "case_insensitive": "skiftlägesoberoende", diff --git a/test/e2e/spec/misc.js b/test/e2e/spec/misc.js index a5b3fc758..103b15c98 100644 --- a/test/e2e/spec/misc.js +++ b/test/e2e/spec/misc.js @@ -48,32 +48,6 @@ describe("page", function () { }) }) -describe("json button", function () { - let elm = null - - beforeEach(async () => { - // browser.ignoreSynchronization = true - const wd = cycleSearch() - await browser.get(browser.params.url + `#?corpus=suc2&cqp=%5B%5D&search=word%7C${wd}&page=7&show_stats`) - }) - - it("should display the correct url", async function () { - const elm = element(by.css("#json-link")) - await waitFor(elm) - await expect(elm.getAttribute("href")).toContain("query?") - }) - - it("should switch url when changing tab", async function () { - const elem = element(by.css(".result_tabs > ul > li:nth-child(2)")) - // waitFor(elem) - await elem.click() - - elm = element(by.css("#json-link")) - await waitFor(elm) - await expect(elm.getAttribute("href")).toContain("count?") - }) -}) - describe("kwic download menu", () => // would love to test that download is really performed but it's hard to test side effects... it("should show the csv download option", async () => {