Każda wystarczająco zaawansowana technologia jest nie do odróżnienia od magii. Chyba że rozumiesz to. Nazywam się Thomas Steiner i pracuję w zespole ds. relacji z deweloperami w Google. W tym podsumowaniu mojego wystąpienia na konferencji Google I/O omówię niektóre nowe interfejsy Fugu API i sposób, w jaki poprawiają one najważniejsze ścieżki użytkowników w aplikacji PWA Excalidraw. Możesz się zainspirować tymi pomysłami i zastosować je w swoich aplikacjach.
Jak trafiłem do Excalidraw
Zacznijmy od historii. 1 stycznia 2020 r. Christopher Chedeau, inżynier oprogramowania w firmie Facebook, wysłał tweeta o małej aplikacji do rysowania, nad którą zaczął pracować. Za pomocą tego narzędzia możesz rysować pola i strzałki, które wyglądają jak rysunki ręczne lub jak elementy rysunkowe. Następnego dnia możesz narysować elipsy i tekst, a także wybrać obiekty i je przenosić. 3 stycznia aplikacja otrzymała nazwę Excalidraw. Podobnie jak w przypadku każdego dobrego projektu pobocznego, jednym z pierwszych działań Christophera było kupienie nazwy domeny. Teraz możesz używać kolorów i eksportować cały rysunek jako plik PNG.
15 stycznia Christopher opublikował post na blogu, który przyciągnął na Twitterze wiele uwagi, w tym moją. Post zaczyna się od imponujących statystyk:
- 12 tys. unikalnych aktywnych użytkowników
- 1,5 tys.gwiazdek w GitHubie
- 26 współtwórców
To całkiem niezły wynik jak na projekt, który rozpoczął się zaledwie 2 tygodnie temu. Jednak to, co naprawdę przykuło moją uwagę, znajdowało się niżej w poście. Christopher napisał, że tym razem wypróbował coś nowego: udostępnił wszystkim, którzy wysłali prośbę o przechwycenie, bezwarunkowy dostęp do zatwierdzania. Tego samego dnia, w którym przeczytałem wpis na blogu, wysłałem prośbę o przejęcie kodu źródłowego, aby dodać do Excalidraw obsługę interfejsu File System Access API, co rozwiązało prośbę o dodanie funkcji, którą ktoś wcześniej przesłał.
Mój request pull został scalony dzień później i od tego czasu miałem pełny dostęp do commitów. Nie muszę chyba mówić, że nie nadużywam swoich uprawnień. Do tej pory nikt z 149 współtwórców nie zgłosił też żadnych problemów.
Obecnie Excalidraw to pełna, instalowana progresywna aplikacja internetowa z obsługą trybu offline, wspaniałym trybem ciemnym i możliwością otwierania i zapisywania plików dzięki interfejsowi File System Access API.
Lipis o tym, dlaczego poświęca tyle czasu na Excalidraw
To już koniec historii o tym, jak zaczęłam używać Excalidraw, ale zanim zaprezentuję niektóre z jego niesamowitych funkcji, chciałabym przedstawić Panayiotisa. Panayiotis Lipiridis, znany w internecie jako lipis, jest najbardziej płodnym autorem w Excalidraw. Zapytaliśmy lipisa, co motywuje go do poświęcania tak dużo czasu na Excalidraw:
Podobnie jak wszyscy inni dowiedziałem się o tym projekcie z twaszego tweeta. Moim pierwszym wkładem było dodanie biblioteki Open Color, czyli kolorów, które są nadal częścią Excalidraw. Gdy projekt się rozwijał i mieliśmy sporo próśb, moim kolejnym dużym wkładem było stworzenie backendu do przechowywania rysunków, aby użytkownicy mogli je udostępniać. Ale to, co naprawdę mnie motywuje do wkładu, to fakt, że każdy, kto wypróbował Excalidraw, szuka wymówek, aby z niego korzystać ponownie.
Całkowicie się zgadzam z opinią lipis. Każdy, kto wypróbował Excalidraw, szuka wymówek, aby z niego znów skorzystać.
Excalidraw w działaniu
Pokażę Ci teraz, jak możesz używać Excalidraw w praktyce. Nie jestem wybitnym artystą, ale logo Google I/O jest dość proste, więc spróbuję. Pole to „i”, linia to „/”, a „o” to koło. Przytrzymaj Shift, aby uzyskać idealny okrąg. Przesuń trochę znak zanikania, aby wyglądał lepiej. Teraz kolory dla „i” i „o”. Niebieski to dobry znak. Może inny styl wypełnienia? Cały pełny czy kreskowany? Nie, hachure wygląda świetnie. Nie jest to idealne, ale taka jest idea Excalidraw, więc zapiszę.
Klikam ikonę zapisywania i w oknie zapisywania pliku wpisuję nazwę pliku. W Chrome, przeglądarce obsługującej interfejs File System Access API, nie jest to pobieranie, ale prawdziwe zapisywanie, w którym mogę wybrać lokalizację i nazwę pliku, a jeśli wprowadzę zmiany, mogę je zapisać w tym samym pliku.
Zmień logo i ustaw czerwoną literę „i”. Jeśli teraz kliknę Zapisz ponownie, moje zmiany zostaną zapisane w tym samym pliku co poprzednio. W celu potwierdzenia czystego obrazu otwórz ponownie plik. Jak widać, zmodyfikowane logo w kolorach czerwonym i niebieskim jest znów widoczne.
Praca z plikami
W przeglądarkach, które obecnie nie obsługują interfejsu API dostępu do systemu plików, każda operacja zapisu jest pobieraniem, więc po wprowadzeniu zmian otrzymuję wiele plików z rosnącą liczbą w nazwie, które wypełniają mój folder Pobrane. Mimo to mogę zapisać plik.
Otwieranie plików
Jaki jest sekret? Jak otwieranie i zapisywanie może działać w różnych przeglądarkach, które mogą lub nie obsługiwać interfejsu API dostępu do systemu plików? Otwieranie pliku w Excalidraw odbywa się w ramach funkcji o nazwie loadFromJSON)(
, która z kolei wywołuje funkcję o nazwie fileOpen()
.
export const loadFromJSON = async (localAppState: AppState) => {
const blob = await fileOpen({
description: 'Excalidraw files',
extensions: ['.json', '.excalidraw', '.png', '.svg'],
mimeTypes: ['application/json', 'image/png', 'image/svg+xml'],
});
return loadFromBlob(blob, localAppState);
};
Funkcja fileOpen()
pochodzi z małej biblioteki o nazwie browser-fs-access, której używamy w Excalidraw. Ta biblioteka zapewnia dostęp do systemu plików za pomocą interfejsu File System Access API z użyciem starszego rozwiązania, dzięki czemu może być używana w dowolnej przeglądarce.
Najpierw pokażę Ci implementację, gdy interfejs API jest obsługiwany. Po negocjowaniu akceptowanych typów MIME i rozszerzeń plików centralnym elementem jest wywołanie funkcji interfejsu File System Access API showOpenFilePicker()
. Ta funkcja zwraca tablicę plików lub pojedynczy plik w zależności od tego, czy wybrano wiele plików. Teraz wystarczy umieścić uchwyt pliku w obiekcie pliku, aby można go było pobrać ponownie.
export default async (options = {}) => {
const accept = {};
// Not shown: deal with extensions and MIME types.
const handleOrHandles = await window.showOpenFilePicker({
types: [
{
description: options.description || '',
accept: accept,
},
],
multiple: options.multiple || false,
});
const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
if (options.multiple) return files;
return files[0];
const getFileWithHandle = async (handle) => {
const file = await handle.getFile();
file.handle = handle;
return file;
};
};
Implementacja zastępcza opiera się na elemencie input
typu "file"
. Po negocjowaniu akceptowanych typów MIME i rozszerzeń należy kliknąć element wejściowy za pomocą kodu, aby otworzyć okno otwierania pliku. Po zmianie, czyli gdy użytkownik wybierze jeden lub więcej plików, obietnica zostanie spełniona.
export default async (options = {}) => {
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
const accept = [
...(options.mimeTypes ? options.mimeTypes : []),
options.extensions ? options.extensions : [],
].join();
input.multiple = options.multiple || false;
input.accept = accept || '*/*';
input.addEventListener('change', () => {
resolve(input.multiple ? Array.from(input.files) : input.files[0]);
});
input.click();
});
};
Zapisywanie plików
Teraz o zapisywaniu. W Excalidraw zapisywanie odbywa się w funkcji o nazwie saveAsJSON()
. Najpierw serializuje tablicę elementów Excalidraw do formatu JSON, a następnie konwertuje ją do pliku blob. Następnie wywołuje funkcję fileSave()
. Ta funkcja jest również udostępniana przez bibliotekę browser-fs-access.
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
type: 'application/vnd.excalidraw+json',
});
const fileHandle = await fileSave(
blob,
{
fileName: appState.name,
description: 'Excalidraw file',
extensions: ['.excalidraw'],
},
appState.fileHandle,
);
return { fileHandle };
};
Ponownie zacznijmy od przeglądarek obsługujących interfejs File System Access API. Pierwsze kilka linii może wyglądać na skomplikowane, ale w istocie służy tylko do negocjowania typów MIME i rozszerzeń plików. Gdy plik został już zapisany i mam już uchwyt pliku, nie musi wyświetlać się okno zapisu. Jeśli jednak jest to pierwszy zapis, wyświetli się okno pliku, a aplikacja otrzyma informacje o pliku na przyszłość. Reszta to tylko zapisywanie w pliku, co odbywa się za pomocą strumenienia do zapisu.
export default async (blob, options = {}, handle = null) => {
options.fileName = options.fileName || 'Untitled';
const accept = {};
// Not shown: deal with extensions and MIME types.
handle =
handle ||
(await window.showSaveFilePicker({
suggestedName: options.fileName,
types: [
{
description: options.description || '',
accept: accept,
},
],
}));
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
};
Funkcja „Zapisz jako”
Jeśli zdecyduję się zignorować istniejący już uchwyt pliku, mogę użyć funkcji „Zapisz jako”, aby utworzyć nowy plik na podstawie istniejącego. Aby to pokazać, otwórz istniejący plik, wprowadź w nim pewne zmiany, a potem nie zastępuj istniejącego pliku, lecz utwórz nowy, korzystając z funkcji Zapisz jako. Oryginalny plik pozostaje niezmieniony.
Implementacja dla przeglądarek, które nie obsługują interfejsu API dostępu do systemu plików, jest krótka, ponieważ polega tylko na utworzeniu elementu kotwicy z atrybutem download
, którego wartość to żądana nazwa pliku, oraz z atrybutem href
zawierającym URL bloba.
export default async (blob, options = {}) => {
const a = document.createElement('a');
a.download = options.fileName || 'Untitled';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', () => {
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};
Następnie element zakotwiczenia jest klikany programowo. Aby zapobiec wyciekom pamięci, po użyciu należy cofnąć adres URL pliku blob. Ponieważ jest to tylko pobieranie, nie wyświetla się okno zapisywania plików, a wszystkie pliki trafiają do domyślnego folderu Downloads
.
Przeciągnij i upuść
Jednym z moich ulubionych systemów integracji na komputerze jest przeciąganie i upuszczanie. Gdy w Excalidraw przeciągnę plik .excalidraw
do aplikacji, otwiera się on od razu i mogę zacząć go edytować. W przeglądarkach, które obsługują interfejs File System Access API, można nawet od razu zapisać zmiany. Nie trzeba otwierać okna zapisywania pliku, ponieważ wymagany identyfikator pliku został uzyskany z operacji przeciągania i upuszczania.
Aby to zrobić, wywołaj funkcję getAsFileSystemHandle()
elementu transfer danych, gdy interfejs File System Access API jest obsługiwany. Następnie przekazuję ten uchwyt pliku do loadFromBlob()
, co możesz pamiętać z kilku poprzednich akapitów. Z plikami można robić wiele rzeczy: otwierać je, zapisywać, zapisywać ponownie, przeciągać i upuszczać. Mój współpracownik Pete opisał wszystkie te sztuczki i nie tylko w tym artykule. Możesz go przeczytać, jeśli nie udało Ci się załapać wszystkiego.
const file = event.dataTransfer?.files[0];
if (file?.type === 'application/json' || file?.name.endsWith('.excalidraw')) {
this.setState({ isLoading: true });
// Provided by browser-fs-access.
if (supported) {
try {
const item = event.dataTransfer.items[0];
file as any.handle = await item as any
.getAsFileSystemHandle();
} catch (error) {
console.warn(error.name, error.message);
}
}
loadFromBlob(file, this.state).then(({ elements, appState }) =>
// Load from blob
).catch((error) => {
this.setState({ isLoading: false, errorMessage: error.message });
});
}
Udostępnianie plików
Inną integracją systemu, która jest obecnie dostępna na Androidzie, ChromeOS i Windows, jest interfejs Web Share Target API. Tutaj jestem w aplikacji Pliki w folderze Downloads
. Widzę 2 pliki, z których jeden ma niejasną nazwę untitled
i sygnaturę czasową. Aby sprawdzić, co zawiera, klikam 3 kropki, a potem udostępniam. Jedną z opcji, która się wyświetla, jest Excalidraw. Gdy klikam ikonę, widzę, że plik zawiera tylko logo I/O.
Lipis w wycofanej wersji Electron
Jedną z rzeczy, które możesz zrobić z plikami, o których jeszcze nie mówiłem, jest ich podwójne kliknięcie. Gdy klikniesz plik dwukrotnie, otwiera się aplikacja powiązana z typem MIME pliku. Na przykład w przypadku .docx
będzie to Microsoft Word.
Excalidraw wcześniej miała wersję Electron, która obsługiwała takie skojarzenia typów plików. Po dwukrotnym kliknięciu pliku .excalidraw
otwierała się aplikacja Electron Excalidraw. Lipis, którego już znasz, był twórcą i osobą, która wycofała Excalidraw Electron. Spytałem, dlaczego uważa, że można wycofać wersję Electron:
Od samego początku użytkownicy prosili o aplikację Electron, głównie dlatego, że chcieli otwierać pliki przez dwukrotne kliknięcie. Chcieliśmy też umieścić aplikację w sklepach z aplikacjami. Ktoś zasugerował też stworzenie aplikacji internetowej, więc zrobiliśmy obie te rzeczy. Na szczęście poznaliśmy interfejsy API projektu Fugu, takie jak dostęp do systemu plików, dostęp do schowka czy obsługa plików. Wystarczy jedno kliknięcie, aby zainstalować aplikację na komputerze lub urządzeniu mobilnym bez dodatkowego obciążenia Electronem. Podjęliśmy łatwą decyzję o wycofaniu wersji Electron i skupieniu się tylko na aplikacji internetowej, aby uczynić z niej najlepszą możliwą aplikację PWA. Co więcej, możesz teraz publikować Progressive Web Apps w Sklepie Play i Microsoft Store. To świetna wiadomość.
Można powiedzieć, że Excalidraw for Electron nie zostało wycofane, ponieważ Electron jest zły, ale dlatego, że internet stał się wystarczająco dobry. Podoba mi się
Obsługa plików
Gdy mówię, że „internet stał się wystarczająco dobry”, to dzięki funkcjom takim jak nadchodzące Zarządzanie plikami.
Jest to zwykła instalacja systemu macOS Big Sur. Sprawdź, co się dzieje, gdy klikam prawym przyciskiem myszy plik programu Excalidraw. Mogę otworzyć go w zainstalowanej aplikacji PWA Excalidraw. Oczywiście można też kliknąć dwukrotnie, ale nie jest to tak efektowne w filmie.
Jak to działa? Pierwszym krokiem jest poinformowanie systemu operacyjnego o typach plików, które może obsługiwać aplikacja. Robię to w nowym polu o nazwie file_handlers
w pliku manifestu aplikacji internetowej. Jego wartość to tablica obiektów z działaniem i właściwością accept
. Działanie określa ścieżkę adresu URL, pod którą system operacyjny uruchamia aplikację, a obiekt accept to pary klucz-wartość typów MIME i powiązanych rozszerzeń plików.
{
"name": "Excalidraw",
"description": "Excalidraw is a whiteboard tool...",
"start_url": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff",
"file_handlers": [
{
"action": "/",
"accept": {
"application/vnd.excalidraw+json": [".excalidraw"]
}
}
]
}
Kolejnym krokiem jest przetworzenie pliku po uruchomieniu aplikacji. Dzieje się tak w interfejsie launchQueue
, w którym muszę ustawić konsumenta, wywołując funkcję setConsumer()
. Parametr tej funkcji to funkcja asynchroniczna, która przyjmuje parametr launchParams
. Obiekt launchParams
ma pole o nazwie files, które zawiera tablicę uchwytów plików. Interesuje mnie tylko ten pierwszy, a z jego uchwytu pliku otrzymuję bloba, który przekazuję naszemu staremu znajomemu loadFromBlob()
.
if ('launchQueue' in window && 'LaunchParams' in window) {
window as any.launchQueue
.setConsumer(async (launchParams: { files: any[] }) => {
if (!launchParams.files.length) return;
const fileHandle = launchParams.files[0];
const blob: Blob = await fileHandle.getFile();
blob.handle = fileHandle;
loadFromBlob(blob, this.state).then(({ elements, appState }) =>
// Initialize app state.
).catch((error) => {
this.setState({ isLoading: false, errorMessage: error.message });
});
});
}
Jeśli to było zbyt szybkie, więcej informacji o interfejsie API do obsługi plików znajdziesz w tym artykule. Obsługę plików możesz włączyć, ustawiając flagę funkcji eksperymentalnej platformy internetowej. Ta funkcja zostanie dodana do Chrome jeszcze w tym roku.
Integracja ze schowkiem
Inną fajną funkcją Excalidraw jest integracja ze schowkiem. Mogę skopiować cały rysunek lub tylko jego fragmenty do schowka, dodać znak wodny, a następnie wkleić je do innej aplikacji. To internetowa wersja aplikacji Paint z Windows 95.
Sposób działania jest zaskakująco prosty. Potrzebuję tylko kanwy jako bloba, który następnie zapisuję w schowku, przekazując tablicę jednoelementową z wartością ClipboardItem
i blobem do funkcji navigator.clipboard.write()
. Więcej informacji o możliwościach korzystania z interfejsu API schowka znajdziesz w artykule Jasona i moim artykule.
export const copyCanvasToClipboardAsPng = async (canvas: HTMLCanvasElement) => {
const blob = await canvasToBlob(canvas);
await navigator.clipboard.write([
new window.ClipboardItem({
'image/png': blob,
}),
]);
};
export const canvasToBlob = async (canvas: HTMLCanvasElement): Promise<Blob> => {
return new Promise((resolve, reject) => {
try {
canvas.toBlob((blob) => {
if (!blob) {
return reject(new CanvasError(t('canvasError.canvasTooBig'), 'CANVAS_POSSIBLY_TOO_BIG'));
}
resolve(blob);
});
} catch (error) {
reject(error);
}
});
};
Współpraca z innymi
Udostępnianie adresu URL sesji
Czy wiesz, że Excalidraw ma też tryb współpracy? Różne osoby mogą pracować nad tym samym dokumentem. Aby rozpocząć nową sesję, klikam przycisk współpracy na żywo, a następnie rozpoczynam sesję. Dzięki zintegrowanemu w Excalidraw interfejsowi Web Share API mogę łatwo udostępniać adres URL sesji współpracownikom.
Współpraca na żywo
Symulowaliśmy sesję współpracy lokalnie, pracując nad logo Google I/O na Pixelbooku, telefonie Pixel 3a i tablecie iPad Pro. Możesz zobaczyć, że zmiany wprowadzone na jednym urządzeniu są odzwierciedlane na wszystkich innych urządzeniach.
Widzę nawet, jak poruszają się wszystkie kursory. Kursor na Pixelbooku porusza się płynnie, ponieważ jest sterowany za pomocą trackpada, ale kursor na telefonie Pixel 3a i na tablecie iPad Pro przeskakuje, ponieważ steruję tymi urządzeniami, dotykając ich palcem.
Wyświetlanie stanów współpracowników
Aby usprawnić współpracę w czasie rzeczywistym, uruchomiliśmy nawet system wykrywania bezczynności. Podczas korzystania z iPada Pro kursor pokazuje zieloną kropkę. Gdy przełączę się na inną kartę przeglądarki lub aplikację, kropka zmieni kolor na czarny. Gdy jestem w aplikacji Excalidraw, ale nic nie robię, kursor pokazuje, że jestem nieaktywny – symbolizują to 3 z z z.
Zagorzali czytelnicy naszych publikacji mogą sądzić, że wykrywanie bezczynności jest realizowane za pomocą interfejsu Idle Detection API, który jest propozycją na wczesnym etapie rozwoju, nad którą pracujemy w ramach projektu Fugu. Uwaga, spoiler: nie jest. W Excalidraw mieliśmy implementację opartą na tym interfejsie API, ale ostatecznie zdecydowaliśmy się na bardziej tradycyjne podejście polegające na pomiarze ruchu wskaźnika i widoczności strony.
Przesłaliśmy opinię na temat tego, dlaczego interfejs API do wykrywania bezczynności nie spełniał naszego przypadku użycia. Wszystkie interfejsy API projektu Fugu są rozwijane w ogólnodostępny sposób, więc każdy może się wypowiedzieć i wyrazić swoją opinię.
Lipis o tym, co powstrzymuje Excalidraw
W związku z tym zapytałem go jeszcze o to, czego jego zdaniem brakuje w tej platformie internetowej, co hamuje rozwój Excalidraw:
Interfejs File System Access API jest świetny, ale wiesz co? Większość plików, które są dla mnie ważne, znajduje się w Dropboxie lub na Dysku Google, a nie na dysku twardym. Chciałbym, aby interfejs File System Access API zawierał warstwę abstrakcji dla dostawców zdalnych systemów plików, takich jak Dropbox czy Google, z którą można się integrować i którą programiści mogą wykorzystywać w kodowaniu. Użytkownicy mogliby mieć pewność, że ich pliki są bezpieczne u zaufanego dostawcy usług w chmurze.
Całkowicie się zgadzam z lipis, bo ja też mieszkam w chmurze. Mam nadzieję, że to zostanie wdrożone w najbliższym czasie.
Tryb aplikacji z kartami
Niesamowite! W Excalidraw znajdziesz wiele naprawdę świetnych integracji interfejsu API. System plików, obsługa plików, Schowek, udostępnianie w internecie i cel udostępniania w internecie. Ale mam jeszcze jedną rzecz. Do tej pory można było edytować tylko 1 dokument naraz. Już nie. Po raz pierwszy możesz skorzystać z wczesnej wersji trybu aplikacji z kartami w Excalidraw. Oto jak to wygląda.
Mam otwarty plik w zainstalowanej aplikacji PWA Excalidraw, która działa w trybie samodzielnym. Teraz otwieram nową kartę w oknie samodzielnym. To nie jest zwykła karta przeglądarki, ale karta PWA. Na nowej karcie mogę otworzyć dodatkowy plik i pracować nad nim niezależnie w tym samym oknie aplikacji.
Tryb aplikacji z kartami jest we wczesnej fazie rozwoju i nie wszystko jest jeszcze ustalone. Jeśli chcesz dowiedzieć się więcej, przeczytaj mój artykuł, w którym znajdziesz aktualny stan tej funkcji.
Zakończenie
Aby być na bieżąco z tą i innymi funkcjami, obserwuj nasz tracker interfejsu Fugu API. Cieszymy się, że możemy rozwijać internet i umożliwiać Ci korzystanie z większej liczby funkcji na tej platformie. Oto stale ulepszane narzędzie Excalidraw i wszystkie wspaniałe aplikacje, które dzięki niemu stworzysz. Zacznij tworzyć na stronie excalidraw.com.
Nie mogę się doczekać, aż niektóre z przedstawionych dzisiaj interfejsów API pojawią się w Twoich aplikacjach. Nazywam się Tom i na Twitterze oraz w internecie ogólnie można mnie znaleźć pod nickiem @tomayac. Dziękujemy za obejrzenie filmu. Do zobaczenia na Google I/O.