Criar, atualizar e excluir arquivos em um contêiner
- 11 minutos
Neste exercício, você atualizará o projeto existente para armazenar arquivos em um contêiner do SharePoint Embedded.
Adicionar declarações de tipo do Microsoft Graph ao projeto
Antes de criarmos os novos componentes do React, vamos começar atualizando nosso projeto.
Ao longo deste exercício, usaremos tipos fornecidos pelo Microsoft Graph. Como não instalamos o pacote npm que os inclui, precisamos fazer isso primeiro.
Na linha de comando, execute o seguinte comando na pasta raiz do nosso projeto:
npm install @microsoft/microsoft-graph-types -DE
Atualize o componente React Containers para exibir arquivos
Lembre-se do exercício anterior, deixamos um espaço reservado em nosso componente Contêineres que será usado para exibir o conteúdo do Contêiner selecionado.
Não criamos nosso Files componente, mas vamos começar atualizando o Containers componente para substituir o espaço reservado Files pelo componente que criaremos.
Localize e abra o arquivo ./src/components/containers.tsx .
Adicione a seguinte instrução de importação à lista de importações existentes na parte superior do arquivo:
import { Files } from "./files";
Em seguida, localize o seguinte código de espaço reservado próximo ao final do arquivo...
{selectedContainer && (`[[TOOD]] container "${selectedContainer.displayName}" contents go here`)}
… e substitua-o pelo seguinte código:
{selectedContainer && (<Files container={selectedContainer} />)}
Criar o componente Files React
Vamos começar criando um novo componente do React para exibir e gerenciar o conteúdo dos Containers.
Crie um novo arquivo, ./src/components/files.tsx, e adicione o código a seguir. Este é o componente clichê que inclui todas as importações e o esqueleto do nosso componente:
import React, {
useState,
useEffect,
useRef
} from 'react';
import { Providers } from "@microsoft/mgt-element";
import {
AddRegular, ArrowUploadRegular,
FolderRegular, DocumentRegular,
SaveRegular, DeleteRegular,
} from '@fluentui/react-icons';
import {
Button, Link, Label, Spinner,
Input, InputProps, InputOnChangeData,
Dialog, DialogActions, DialogContent, DialogBody, DialogSurface, DialogTitle, DialogTrigger,
DataGrid, DataGridProps,
DataGridHeader, DataGridHeaderCell,
DataGridBody, DataGridRow,
DataGridCell,
TableColumnDefinition, createTableColumn,
TableRowId,
TableCellLayout,
OnSelectionChangeData,
SelectionItemId,
Toolbar, ToolbarButton,
makeStyles
} from "@fluentui/react-components";
import {
DriveItem
} from "@microsoft/microsoft-graph-types-beta";
import { IContainer } from "./../common/IContainer";
require('isomorphic-fetch');
interface IFilesProps {
container: IContainer;
}
interface IDriveItemExtended extends DriveItem {
isFolder: boolean;
modifiedByName: string;
iconElement: JSX.Element;
downloadUrl: string;
}
export const Files = (props: IFilesProps) => {
// BOOKMARK 1 - constants & hooks
// BOOKMARK 2 - handlers go here
// BOOKMARK 3 - component rendering return (
return
(
<div>
</div>
);
}
export default Files;
Observação
Observe os // BOOKMARK # comentários no componente. Isso garante que você esteja adicionando código nos locais corretos.
Exibir uma lista do conteúdo selecionado do Contêiner
A primeira coisa que precisamos abordar é exibir o conteúdo do contêiner selecionado. Para fazer isso, usaremos o DataGrid componente da biblioteca Fluent UI React.
Adicione a seguinte marcação dentro do <div> elemento na return() instrução após o // BOOKMARK 3 comentário:
<DataGrid
items={driveItems}
columns={columns}
getRowId={(item) => item.id}
resizableColumns
columnSizingOptions={columnSizingOptions}
>
<DataGridHeader>
<DataGridRow>
{({ renderHeaderCell }) => (
<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
)}
</DataGridRow>
</DataGridHeader>
<DataGridBody<IDriveItemExtended>>
{({ item, rowId }) => (
<DataGridRow<IDriveItemExtended> key={rowId}>
{({ renderCell, columnId }) => (
<DataGridCell>
{renderCell(item)}
</DataGridCell>
)}
</DataGridRow>
)}
</DataGridBody>
</DataGrid>
O DataGrid contém algumas referências a coleções, configurações e métodos que precisamos configurar. Vamos começar com as coisas visuais, depois obteremos os dados.
As colunas no DataGrid podem ser redimensionadas de acordo com as propriedades que definimos. Crie uma nova constante columnSizingOptionse adicione o código imediatamente antes do // BOOKMARK 3 comentário:
const columnSizingOptions = {
driveItemName: {
minWidth: 150,
defaultWidth: 250,
idealWidth: 200
},
lastModifiedTimestamp: {
minWidth: 150,
defaultWidth: 150
},
lastModifiedBy: {
minWidth: 150,
defaultWidth: 150
},
actions: {
minWidth: 250,
defaultWidth: 250
}
};
Em seguida, defina as configurações de estrutura e renderização de todas as colunas no arquivo DataGrid. Para fazer isso, columnscrie uma nova coleção e adicione-a imediatamente antes da columnSizingOptions criação:
const columns: TableColumnDefinition<IDriveItemExtended>[] = [
createTableColumn({
columnId: 'driveItemName',
renderHeaderCell: () => {
return 'Name'
},
renderCell: (driveItem) => {
return (
<TableCellLayout media={driveItem.iconElement}>
<Link href={driveItem!.webUrl!} target='_blank'>{driveItem.name}</Link>
</TableCellLayout>
)
}
}),
createTableColumn({
columnId: 'lastModifiedTimestamp',
renderHeaderCell: () => {
return 'Last Modified'
},
renderCell: (driveItem) => {
return (
<TableCellLayout>
{driveItem.lastModifiedDateTime}
</TableCellLayout>
)
}
}),
createTableColumn({
columnId: 'lastModifiedBy',
renderHeaderCell: () => {
return 'Last Modified By'
},
renderCell: (driveItem) => {
return (
<TableCellLayout>
{driveItem.modifiedByName}
</TableCellLayout>
)
}
}),
createTableColumn({
columnId: 'actions',
renderHeaderCell: () => {
return 'Actions'
},
renderCell: (driveItem) => {
return (
<>
<Button aria-label="Download"
disabled={!selectedRows.has(driveItem.id as string)}
icon={<SaveRegular />}>Download</Button>
<Button aria-label="Delete"
icon={<DeleteRegular />}>Delete</Button>
</>
)
}
}),
];
Esse código usará o método createTableColumn() utilitário para dar uma ID a cada coluna e especificar como as células do cabeçalho e do corpo da tabela são renderizadas.
Com o DataGrid configurado, adicione as constantes a seguir para gerenciar o estado do aplicativo React com as propriedades que nosso código existente está usando. Adicione o seguinte código imediatamente antes do comentário // BOOKMARK 1:
const [driveItems, setDriveItems] = useState<IDriveItemExtended[]>([]);
const [selectedRows, setSelectedRows] = useState<Set<SelectionItemId>>(new Set<TableRowId>([1]));
Agora, vamos adicionar alguns manipuladores para buscar e exibir os dados do nosso Container.
Adicione o manipulador a seguir e o gancho do React para obter o conteúdo do contêiner selecionado. O useEffect gancho será executado na primeira vez que o componente for renderizado, bem como quando as <Files />propriedades de entrada do componente forem alteradas.
Adicione o seguinte código imediatamente antes do comentário // BOOKMARK 2:
useEffect(() => {
(async () => {
loadItems();
})();
}, [props]);
const loadItems = async (itemId?: string) => {
try {
const graphClient = Providers.globalProvider.graph.client;
const driveId = props.container.id;
const driveItemId = itemId || 'root';
// get Container items at current level
const graphResponse = await graphClient.api(`/drives/${driveId}/items/${driveItemId}/children`).get();
const containerItems: DriveItem[] = graphResponse.value as DriveItem[]
const items: IDriveItemExtended[] = [];
containerItems.forEach((driveItem: DriveItem) => {
items.push({
...driveItem,
isFolder: (driveItem.folder) ? true : false,
modifiedByName: (driveItem.lastModifiedBy?.user?.displayName) ? driveItem.lastModifiedBy!.user!.displayName : 'unknown',
iconElement: (driveItem.folder) ? <FolderRegular /> : <DocumentRegular />,
downloadUrl: (driveItem as any)['@microsoft.graph.downloadUrl']
});
});
setDriveItems(items);
} catch (error: any) {
console.error(`Failed to load items: ${error.message}`);
}
};
A loadItems função usa o cliente do Microsoft Graph para obter uma lista de todos os arquivos na pasta atual, padronizando para se root nenhuma pasta já estiver selecionada.
Em seguida, ele pega a coleção de DriveItems retornados pelo Microsoft Graph e adiciona mais algumas propriedades para simplificar nosso código posteriormente. No final do método, ele chama o setDriveitems() método acessador de estado que acionará uma nova renderização do componente. Eles driveItems são definidos na DataGrid.items propriedade que explica por que a tabela exibe algumas informações.
Testar a renderização da listagem do conteúdo de um contêiner
Agora vamos testar o aplicativo React do lado do cliente para garantir que o <Files /> componente esteja exibindo o conteúdo do contêiner selecionado.
Na linha de comando na pasta raiz do projeto, execute o seguinte comando:
npm run start
Quando o navegador for carregado, entre usando a mesma conta corporativa ou de estudante que você estava usando.
Depois de entrar, selecione um contêiner existente. Se esse contêiner já tiver algum conteúdo, ele será exibido da seguinte maneira:
Se você selecionar o arquivo, nesse caso um documento do Word, ele abrirá uma nova guia e carregará a URL do item. Neste exemplo, o arquivo é aberto no Word online.
Adicionar suporte para download de arquivos
Com o recurso de exibição de conteúdo concluído, vamos atualizar o componente para oferecer suporte ao download de arquivos.
Comece adicionando o seguinte código imediatamente antes do // BOOKMARK 1 comentário:
const downloadLinkRef = useRef<HTMLAnchorElement>(null);
Em seguida, queremos verificar se um item no DataGrid está selecionado antes que eles possam baixá-lo. Caso contrário, o botão Download será desabilitado como está atualmente.
DataGridNo , adicione três propriedades para configurá-lo para dar suporte a um modo de seleção de item único (selectionMode), acompanhe quais itens são selecionados (selectedItems) e o que fazer quando a seleção for alterada (onSelectionChange).
<DataGrid
...
selectionMode='single'
selectedItems={selectedRows}
onSelectionChange={onSelectionChange}>
Em seguida, adicione o seguinte manipulador imediatamente antes do // BOOKMARK 2 comentário:
const onSelectionChange: DataGridProps["onSelectionChange"] = (event: React.MouseEvent | React.KeyboardEvent, data: OnSelectionChangeData): void => {
setSelectedRows(data.selectedItems);
}
Agora, quando um item na lista for selecionado, você verá que o botão Download não está mais desabilitado.
A opção de download usará um hiperlink oculto no qual primeiro definiremos programaticamente o link de download do item selecionado e, em seguida, faremos o seguinte de forma programática:
- Defina a URL do hiperlink para a URL de download do item.
- Selecione o hiperlink.
Isso acionará o download para o usuário.
Adicione a seguinte marcação logo após a abertura <div> no return() método:
<a ref={downloadLinkRef} href="" target="_blank" style={{ display: 'none' }} />
Agora, localize a constante existente columns que você adicionou anteriormente e localize a createTableColumn que faz referência ao columnId: 'actions'.
renderCell Na propriedade, adicione um onClick manipulador que chamará o onDownloadItemClick. O botão deve ter a seguinte aparência quando você concluir:
<Button aria-label="Download"
disabled={!selectedRows.has(driveItem.id as string)}
icon={<SaveRegular />}
onClick={() => onDownloadItemClick(driveItem.downloadUrl)}>Download</Button>
Por fim, adicione o manipulador a seguir imediatamente após o manipulador de eventos existente onSelectionChange que você adicionou anteriormente. Isso lidará com as duas etapas programáticas mencionadas anteriormente:
const onDownloadItemClick = (downloadUrl: string) => {
const link = downloadLinkRef.current;
link!.href = downloadUrl;
link!.click();
}
Salve suas alterações, atualize o navegador e selecione o link Baixar para ver o arquivo ser baixado.
Adicionar a capacidade de criar uma pasta em um contêiner
Vamos continuar desenvolvendo o <Files /> componente adicionando suporte para criar e exibir pastas.
Comece adicionando o código a seguir imediatamente antes do // BOOKMARK 1 comentário. Isso adicionará os valores de estado do React necessários que usaremos:
const [folderId, setFolderId] = useState<string>('root');
const [folderName, setFolderName] = useState<string>('');
const [creatingFolder, setCreatingFolder] = useState<boolean>(false);
const [newFolderDialogOpen, setNewFolderDialogOpen] = useState(false);
Para criar uma nova pasta, exibiremos uma caixa de diálogo para o usuário quando ele selecionar um botão na barra de ferramentas.
Dentro do return() método, imediatamente antes de <DataGrid>, adicione o seguinte código para implementar a caixa de diálogo:
<Toolbar>
<ToolbarButton vertical icon={<AddRegular />} onClick={() => setNewFolderDialogOpen(true)}>New Folder</ToolbarButton>
</Toolbar>
<Dialog open={newFolderDialogOpen}>
<DialogSurface>
<DialogBody>
<DialogTitle>Create New Folder</DialogTitle>
<DialogContent className={styles.dialogContent}>
<Label htmlFor={folderName}>Folder name:</Label>
<Input id={folderName} className={styles.dialogInputControl} autoFocus required
value={folderName} onChange={onHandleFolderNameChange}></Input>
{creatingFolder &&
<Spinner size='medium' label='Creating folder...' labelPosition='after' />
}
</DialogContent>
<DialogActions>
<DialogTrigger disableButtonEnhancement>
<Button appearance="secondary" onClick={() => setNewFolderDialogOpen(false)} disabled={creatingFolder}>Cancel</Button>
</DialogTrigger>
<Button appearance="primary"
onClick={onFolderCreateClick}
disabled={creatingFolder || (folderName === '')}>Create Folder</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
A caixa de diálogo usa alguns estilos personalizados que ainda não adicionamos. Faça isso agora adicionando primeiro o seguinte código imediatamente antes da declaração do Files componente:
const useStyles = makeStyles({
dialogInputControl: {
width: '400px',
},
dialogContent: {
display: 'flex',
flexDirection: 'column',
rowGap: '10px',
marginBottom: '25px'
}
});
Em seguida, adicione o seguinte código imediatamente antes do return() método em nosso componente:
const styles = useStyles();
Agora que a interface do usuário está configurada, precisamos adicionar alguns manipuladores. Adicione os manipuladores a seguir imediatamente antes do // BOOKMARK 2 comentário. Eles cuidarão da abertura da caixa de diálogo, salvando o valor do nome da nova pasta e o que acontece quando selecionam o botão na caixa de diálogo:
const onFolderCreateClick = async () => {
setCreatingFolder(true);
const currentFolderId = folderId;
const graphClient = Providers.globalProvider.graph.client;
const endpoint = `/drives/${props.container.id}/items/${currentFolderId}/children`;
const data = {
"name": folderName,
"folder": {},
"@microsoft.graph.conflictBehavior": "rename"
};
await graphClient.api(endpoint).post(data);
await loadItems(currentFolderId);
setCreatingFolder(false);
setNewFolderDialogOpen(false);
};
const onHandleFolderNameChange: InputProps["onChange"] = (event: React.ChangeEvent<HTMLInputElement>, data: InputOnChangeData): void => {
setFolderName(data?.value);
};
Salve suas alterações, atualize o navegador e selecione o botão Nova Pasta acima do conteúdo do Contêiner:
Selecione o botão Nova pasta , insira um nome e selecione o botão Criar pasta na caixa de diálogo.
Quando a pasta for criada, você a verá listada na tabela de conteúdo:
Precisamos fazer mais uma mudança em nosso componente. No momento, quando você seleciona uma pasta, ele inicia a URL em uma nova guia, deixando o aplicativo de fora. Não é isso que queremos... Queremos que ele analise detalhadamente a pasta.
Vamos corrigir isso localizando a constante existente columns que você adicionou anteriormente e encontrar a createTableColumn que faz referência ao columnId: 'driveItemName'.
renderCell Na propriedade, substitua o componente existente <Link /> pelo código a seguir. Isso gerará dois links com base em se o item atual que está sendo renderizado for uma pasta ou um arquivo:
{(!driveItem.isFolder)
? <Link href={driveItem!.webUrl!} target='_blank'>{driveItem.name}</Link>
: <Link onClick={() => {
loadItems(driveItem.id);
setFolderId(driveItem.id as string)
}}>{driveItem.name}</Link>
}
Agora, quando você seleciona uma pasta, o aplicativo mostrará o conteúdo da pasta.
Adicionar a capacidade de excluir um arquivo ou pasta
A próxima etapa é adicionar a capacidade de excluir uma pasta ou arquivo do contêiner.
Para fazer isso, comece adicionando o seguinte código à lista existente de useState() chamadas antes do // BOOKMARK 1 comentário
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
Em seguida, adicione uma caixa de diálogo para atuar como uma confirmação quando o usuário selecionar o botão Excluir . Adicione o seguinte código logo após o componente existente <Dialog> no return() método:
<Dialog open={deleteDialogOpen} modalType='modal' onOpenChange={() => setSelectedRows(new Set<TableRowId>([0]))}>
<DialogSurface>
<DialogBody>
<DialogTitle>Delete Item</DialogTitle>
<DialogContent>
<p>Are you sure you want to delete this item?</p>
</DialogContent>
<DialogActions>
<DialogTrigger>
<Button
appearance='secondary'
onClick={() => setDeleteDialogOpen(false)}>Cancel</Button>
</DialogTrigger>
<Button
appearance='primary'
onClick={onDeleteItemClick}>Delete</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
Atualize o botão Excluir para fazer algo quando ele for selecionado. Localize a constante existente columns que você adicionou anteriormente e localize a createTableColumn que faz referência ao columnId: 'actions'.
renderCell Na propriedade, adicione um onClick manipulador que chamará o onDeleteDialogOpen. O botão deve ter a seguinte aparência quando você concluir:
<Button aria-label="Delete"
icon={<DeleteRegular />}
onClick={() => setDeleteDialogOpen(true)}>Delete</Button>
Por fim, adicione o seguinte código imediatamente antes // BOOKMARK 2 do comentário para lidar com a exclusão do item selecionado no momento:
const onDeleteItemClick = async () => {
const graphClient = Providers.globalProvider.graph.client;
const endpoint = `/drives/${props.container.id}/items/${selectedRows.entries().next().value[0]}`;
await graphClient.api(endpoint).delete();
await loadItems(folderId || 'root');
setDeleteDialogOpen(false);
}
Salve suas alterações e atualize o navegador. Selecione o botão Excluir em uma das pastas ou arquivos existentes em sua Coleção. A caixa de diálogo de confirmação será exibida e, quando você selecionar o botão Excluir na caixa de diálogo, a tabela de conteúdo do Contêiner será atualizada para mostrar que o item foi excluído:
Adicionar a capacidade de carregar arquivos no Contêiner
A última etapa é adicionar a capacidade de carregar arquivos em um contêiner ou pasta dentro de um contêiner.
Comece adicionando o seguinte código imediatamente antes do // BOOKMARK 1 comentário:
const uploadFileRef = useRef<HTMLInputElement>(null);
Em seguida, repetiremos uma técnica semelhante usando um controle oculto <Input> para carregar um arquivo. Adicione o seguinte código imediatamente após a abertura <div> no método do return() componente:
<input ref={uploadFileRef} type="file" onChange={onUploadFileSelected} style={{ display: 'none' }} />
Adicione um botão à barra de ferramentas para disparar a caixa de diálogo de seleção de arquivo. Para isso, adicione o seguinte código imediatamente após o botão da barra de ferramentas existente que adiciona uma nova pasta:
<ToolbarButton vertical icon={<ArrowUploadRegular />} onClick={onUploadFileClick}>Upload File</ToolbarButton>
Por fim, adicione o código a seguir imediatamente antes do // BOOKMARK 2 comentário para adicionar dois manipuladores de eventos. O onUploadFileClick manipulador é disparado quando você seleciona o botão Carregar Arquivo da barra de ferramentas e o manipulador é disparado onUploadFileSelected quando o usuário seleciona um arquivo:
const onUploadFileClick = () => {
if (uploadFileRef.current) {
uploadFileRef.current.click();
}
};
const onUploadFileSelected = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files![0];
const fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.addEventListener('loadend', async (event: any) => {
const graphClient = Providers.globalProvider.graph.client;
const endpoint = `/drives/${props.container.id}/items/${folderId || 'root'}:/${file.name}:/content`;
graphClient.api(endpoint).putStream(fileReader.result)
.then(async (response) => {
await loadItems(folderId || 'root');
})
.catch((error) => {
console.error(`Failed to upload file ${file.name}: ${error.message}`);
});
});
fileReader.addEventListener('error', (event: any) => {
console.error(`Error on reading file: ${event.message}`);
});
};
Teste as alterações salvando o arquivo, atualizando o navegador e selecionando o botão Carregar arquivo :
Depois de selecionar um arquivo, nosso aplicativo fará o upload do arquivo e atualizará a tabela do conteúdo do contêiner:
Resumo
Neste exercício, você atualizou o projeto existente para armazenar e gerenciar arquivos em um contêiner do SharePoint Embedded.
Verifique seu conhecimento
Comentários
Esta página foi útil?
No
Precisa de ajuda com este tópico?
Quer experimentar o Pergunte e aprenda para esclarecer ou guiar você neste tópico?