Skip to content

Commit c89eb9f

Browse files
committed
feat(showcase): add CSV import plugin with preview functionality
1 parent 77eb707 commit c89eb9f

File tree

16 files changed

+565
-1
lines changed

16 files changed

+565
-1
lines changed

app/[lang]/(home)/showcase/[...slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default async function Page({ params }: IProps) {
7272
return (
7373
<div className="flex flex-1">
7474
<aside className="h-auto w-72 overflow-x-hidden">
75-
<div className="fixed top-14 h-[calc(100%-110px)] p-4">
75+
<div className="fixed top-14 h-[calc(100%-110px)] w-72 p-4">
7676
<ScrollArea className="h-full">
7777
{Object.entries(groupedNav).map(([type, items]) => (
7878
<div key={type} className="mb-4">

content/guides/recipes/practices/csv-import-plugin.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This plugin allows users to import CSV files into Univer tables.
2424

2525
Let's experience the effect of this plugin first:
2626

27+
<PlaygroundFrame lang="en-US" slug="sheets/csv-import-plugin" clickToShow />
28+
2729
Case source code reference:
2830

2931
- [Plugin Installation](https://github.com/dream-num/univer/blob/dev/examples/src/sheets/custom-plugin/import-csv-button.ts)

content/guides/recipes/practices/csv-import-plugin.zh-CN.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ title: 编写一个 CSV 导入插件
2424

2525
我们先来体验一下这个插件的效果:
2626

27+
<PlaygroundFrame lang="zh-CN" slug="sheets/csv-import-plugin" clickToShow />
28+
2729
案例源码参考:
2830

2931
- [Plugin 安装](https://github.com/dream-num/univer/blob/dev/examples/src/sheets/custom-plugin/import-csv-button.ts)

showcase/data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const showcase: Record<string, Promise<{ default: {
1616
'sheets/basic-via-preset': import('./sheets/basic-via-preset'),
1717
'sheets/lit': import('./sheets/lit'),
1818
'sheets/big-data': import('./sheets/big-data'),
19+
'sheets/csv-import-plugin': import('./sheets/csv-import-plugin'),
1920
'sheets/custom-canvas': import('./sheets/custom-canvas'),
2021
'sheets/crosshair-highlighting': import('./sheets/crosshair-highlighting'),
2122
'docs/slim-via-plugin': import('./docs/slim-via-plugin'),
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { IWorkbookData } from '@univerjs/core'
2+
3+
export const WORKBOOK_DATA: Partial<IWorkbookData> = {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Univer</title>
5+
<meta charset="UTF-8" />
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
<script src="index.js"></script>
10+
</body>
11+
</html>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core'
2+
import sheetsCoreEnUS from '@univerjs/preset-sheets-core/locales/en-US'
3+
import { createUniver, LocaleType, merge } from '@univerjs/presets'
4+
import { WORKBOOK_DATA } from './data'
5+
import { ImportCSVButtonPlugin } from './plugin'
6+
7+
import './styles.css'
8+
9+
import '@univerjs/preset-sheets-core/lib/index.css'
10+
11+
const { univerAPI } = createUniver({
12+
locale: LocaleType.EN_US,
13+
locales: {
14+
[LocaleType.EN_US]: merge(
15+
{},
16+
sheetsCoreEnUS,
17+
),
18+
},
19+
presets: [
20+
UniverSheetsCorePreset(),
21+
],
22+
plugins: [
23+
ImportCSVButtonPlugin,
24+
],
25+
})
26+
27+
univerAPI.createWorkbook(WORKBOOK_DATA)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import type { ICommand, IMutationInfo, Workbook } from '@univerjs/presets'
2+
import type {
3+
ISetRangeValuesMutationParams,
4+
ISetWorksheetColumnCountMutationParams,
5+
ISetWorksheetRowCountMutationParams,
6+
} from '@univerjs/presets/preset-sheets-core'
7+
import { FolderIcon } from '@univerjs/icons'
8+
import {
9+
CommandType,
10+
covertCellValues,
11+
ICommandService,
12+
Inject,
13+
Injector,
14+
IUndoRedoService,
15+
IUniverInstanceService,
16+
Plugin,
17+
sequenceExecute,
18+
UniverInstanceType,
19+
} from '@univerjs/presets'
20+
import {
21+
ComponentManager,
22+
IMenuManagerService,
23+
MenuItemType,
24+
RibbonStartGroup,
25+
SetRangeValuesMutation,
26+
SetRangeValuesUndoMutationFactory,
27+
SetWorksheetColumnCountMutation,
28+
SetWorksheetColumnCountUndoMutationFactory,
29+
SetWorksheetRowCountMutation,
30+
SetWorksheetRowCountUndoMutationFactory,
31+
} from '@univerjs/presets/preset-sheets-core'
32+
import { waitUserSelectCSVFile } from './utils'
33+
34+
/**
35+
* Import CSV Button Plugin
36+
* A simple Plugin example, show how to write a plugin.
37+
*/
38+
export class ImportCSVButtonPlugin extends Plugin {
39+
static override pluginName = 'import-csv-plugin'
40+
41+
constructor(
42+
// inject injector, required
43+
@Inject(Injector) readonly _injector: Injector,
44+
// inject menu service, to add toolbar button
45+
@Inject(IMenuManagerService) private readonly menuManagerService: IMenuManagerService,
46+
// inject command service, to register command handler
47+
@Inject(ICommandService) private readonly commandService: ICommandService,
48+
// inject component manager, to register icon component
49+
@Inject(ComponentManager) private readonly componentManager: ComponentManager,
50+
) {
51+
super()
52+
}
53+
54+
/**
55+
* The first lifecycle of the plugin mounted on the Univer instance,
56+
* the Univer business instance has not been created at this time.
57+
* The plugin should add its own module to the dependency injection system at this lifecycle.
58+
* It is not recommended to initialize the internal module of the plugin outside this lifecycle.
59+
*/
60+
61+
override onStarting() {
62+
// register icon component
63+
this.componentManager.register('FolderIcon', FolderIcon)
64+
65+
const buttonId = 'import-csv-button'
66+
67+
const command: ICommand = {
68+
type: CommandType.OPERATION,
69+
id: buttonId,
70+
handler: (accessor) => {
71+
// inject univer instance service
72+
const univerInstanceService = accessor.get(IUniverInstanceService)
73+
const commandService = accessor.get(ICommandService)
74+
const undoRedoService = accessor.get(IUndoRedoService)
75+
76+
// get current sheet
77+
const worksheet = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet()
78+
const unitId = worksheet.getUnitId()
79+
const subUnitId = worksheet.getSheetId()
80+
81+
// wait user select csv file, then assemble multiple mutations operation to enable correct undo/redo
82+
return waitUserSelectCSVFile(({ data, rowsCount, colsCount }) => {
83+
const redoMutations: IMutationInfo[] = []
84+
const undoMutations: IMutationInfo[] = []
85+
86+
// set sheet row count
87+
const setRowCountMutationRedoParams: ISetWorksheetRowCountMutationParams = {
88+
unitId,
89+
subUnitId,
90+
rowCount: rowsCount,
91+
}
92+
const setRowCountMutationUndoParams: ISetWorksheetRowCountMutationParams = SetWorksheetRowCountUndoMutationFactory(
93+
accessor,
94+
setRowCountMutationRedoParams,
95+
)
96+
redoMutations.push({ id: SetWorksheetRowCountMutation.id, params: setRowCountMutationRedoParams })
97+
undoMutations.push({ id: SetWorksheetRowCountMutation.id, params: setRowCountMutationUndoParams })
98+
99+
// set sheet column count
100+
const setColumnCountMutationRedoParams: ISetWorksheetColumnCountMutationParams = {
101+
unitId,
102+
subUnitId,
103+
columnCount: colsCount,
104+
}
105+
const setColumnCountMutationUndoParams: ISetWorksheetColumnCountMutationParams = SetWorksheetColumnCountUndoMutationFactory(
106+
accessor,
107+
setColumnCountMutationRedoParams,
108+
)
109+
redoMutations.push({ id: SetWorksheetColumnCountMutation.id, params: setColumnCountMutationRedoParams })
110+
undoMutations.unshift({ id: SetWorksheetColumnCountMutation.id, params: setColumnCountMutationUndoParams })
111+
112+
// parse csv to univer data
113+
const cellValue = covertCellValues(data, {
114+
startColumn: 0, // start column index
115+
startRow: 0, // start row index
116+
endColumn: colsCount - 1, // end column index
117+
endRow: rowsCount - 1, // end row index
118+
})
119+
120+
// set sheet data
121+
const setRangeValuesMutationRedoParams: ISetRangeValuesMutationParams = {
122+
unitId,
123+
subUnitId,
124+
cellValue,
125+
}
126+
const setRangeValuesMutationUndoParams: ISetRangeValuesMutationParams = SetRangeValuesUndoMutationFactory(
127+
accessor,
128+
setRangeValuesMutationRedoParams,
129+
)
130+
redoMutations.push({ id: SetRangeValuesMutation.id, params: setRangeValuesMutationRedoParams })
131+
undoMutations.unshift({ id: SetRangeValuesMutation.id, params: setRangeValuesMutationUndoParams })
132+
133+
const result = sequenceExecute(redoMutations, commandService)
134+
135+
if (result.result) {
136+
undoRedoService.pushUndoRedo({
137+
unitID: unitId,
138+
undoMutations,
139+
redoMutations,
140+
})
141+
142+
return true
143+
}
144+
145+
return false
146+
})
147+
},
148+
}
149+
150+
const menuItemFactory = () => ({
151+
id: buttonId,
152+
title: 'Import CSV',
153+
tooltip: 'Import CSV',
154+
icon: 'FolderIcon', // icon name
155+
type: MenuItemType.BUTTON,
156+
})
157+
158+
this.menuManagerService.mergeMenu({
159+
[RibbonStartGroup.OTHERS]: {
160+
[buttonId]: {
161+
order: 10,
162+
menuItemFactory,
163+
},
164+
},
165+
})
166+
167+
this.commandService.registerCommand(command)
168+
}
169+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
html,
2+
body,
3+
#app {
4+
height: 100%;
5+
margin: 0;
6+
padding: 0;
7+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* wait user select csv file
3+
*/
4+
export function waitUserSelectCSVFile(onSelect: (data: {
5+
data: string[][]
6+
colsCount: number
7+
rowsCount: number
8+
}) => boolean): Promise<boolean> {
9+
return new Promise((resolve) => {
10+
const input = document.createElement('input')
11+
input.type = 'file'
12+
input.accept = '.csv'
13+
input.click()
14+
15+
input.onchange = () => {
16+
const file = input.files?.[0]
17+
if (!file) return
18+
const reader = new FileReader()
19+
reader.onload = () => {
20+
const text = reader.result
21+
if (typeof text !== 'string') return
22+
23+
// tip: use npm package to parse csv
24+
const rows = text.split(/\r\n|\n/)
25+
const data = rows.map(line => line.split(','))
26+
27+
const colsCount = data.reduce((max, row) => Math.max(max, row.length), 0)
28+
29+
const result = onSelect({
30+
data,
31+
colsCount,
32+
rowsCount: data.length,
33+
})
34+
35+
resolve(result)
36+
}
37+
reader.readAsText(file)
38+
}
39+
})
40+
}

0 commit comments

Comments
 (0)