blob: a90b81c6864a453b30b8fa1a29d0e255456a82ee [file] [edit]
// Copyright (C) 2025 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import m from 'mithril';
import {download} from '../base/download_utils';
import {Icons} from '../base/semantic_icons';
import {Button} from './button';
import {ActionButtonHelper} from './action_button_helper';
import {copyToClipboard} from '../base/clipboard';
import {MenuItem, PopupMenu} from './menu';
export type ExportFormat = 'tsv' | 'json' | 'markdown';
export interface ExportButtonAttrs {
readonly onExportData: (format: ExportFormat) => Promise<string>;
// Base name used for downloaded files. Defaults to 'data_export'.
readonly fileBaseName?: string;
}
/**
* Export button component with dropdown menu for copying data to clipboard
* or downloading it in different formats (TSV, Markdown, JSON).
* Maintains its own ActionButtonHelper state to show "Copied" feedback.
*/
export class ExportButton implements m.ClassComponent<ExportButtonAttrs> {
private helper = new ActionButtonHelper();
private async copyToClipboardWithHelper(content: Promise<string>) {
await this.helper.execute(async () => await copyToClipboard(await content));
}
view({attrs}: m.CVnode<ExportButtonAttrs>) {
const {onExportData, fileBaseName} = attrs;
const loading = this.helper.state === 'working';
const icon = this.helper.state === 'done' ? Icons.Check : Icons.Download;
const baseName = fileBaseName ?? 'data_export';
return m(
PopupMenu,
{
trigger: m(Button, {
icon,
loading,
label: 'Export',
title: 'Export data',
}),
},
[
m(MenuItem, {label: 'Copy to clipboard'}, [
m(MenuItem, {
label: 'Tab Separated Values',
icon: 'tsv',
title: 'Tab-separated values - paste into spreadsheets',
onclick: async () => {
await this.copyToClipboardWithHelper(onExportData('tsv'));
},
}),
m(MenuItem, {
label: 'Markdown Table',
icon: 'table',
title: 'Markdown table format',
onclick: async () => {
await this.copyToClipboardWithHelper(onExportData('markdown'));
},
}),
m(MenuItem, {
label: 'JSON',
icon: 'data_object',
title: 'JSON array of objects',
onclick: async () => {
await this.copyToClipboardWithHelper(onExportData('json'));
},
}),
]),
m(MenuItem, {label: 'Download'}, [
m(MenuItem, {
label: 'Tab Separated Values (.tsv)',
icon: 'tsv',
title: 'Tab-separated values - opens in Excel/Sheets',
onclick: async () => {
const content = await onExportData('tsv');
download({
content,
mimeType: 'text/tab-separated-values',
fileName: `${baseName}.tsv`,
});
},
}),
m(MenuItem, {
label: 'Markdown Table (.md)',
icon: 'table',
title: 'Markdown table format - paste into docs',
onclick: async () => {
const content = await onExportData('markdown');
download({
content,
mimeType: 'text/markdown',
fileName: `${baseName}.md`,
});
},
}),
m(MenuItem, {
label: 'JSON (.json)',
icon: 'data_object',
title: 'JSON array - use in scripts/tools',
onclick: async () => {
const content = await onExportData('json');
download({
content,
mimeType: 'application/json',
fileName: `${baseName}.json`,
});
},
}),
]),
],
);
}
}