Будівництво веб-додатку для вивчення Vim

Від ідеї до розгортання, частина 1.

pic

Мені подобається Vim. Я вважаю, що це чудовий спосіб зосередитися на важливих аспектах програмування, і приємно, коли ти оптимізуєш хоча б півсекунди свого дня багато разів на кожному завданні. І хоча я не вважаю, що використання Vim є життєво важливим вибором, це безумовно покращує якість життя.

На мою думку, велика причина, чому багато програмістів і навіть звичайних користувачів комп’ютерів не вивчають Vim, полягає в тому, що більшість онлайн-інструментів для навчання або коштують грошей, або є просто стінами тексту без реальної практики. Хоча viminstructor є хорошим ресурсом, йому не вистачає хорошого UX. І тому треба справді любити ідею, щоб пройти через громіздкий процес його освоєння, або бути готовим заплатити за навчання чогось, що спочатку може бути незрозумілим.

Тому, щоб покращити своє розуміння та навички роботи з Vim найменш ефективним способом навчання, який я міг придумати, і дати спільноті хороший інструмент для його вивчення, я вирішив створити онлайн-додаток Vim на React, наповнений вправами, поділеними на розділи, які можна практикувати в будь-який час.

Це ще в процесі розробки, але як тільки додаток запрацює в Інтернеті, я оновлю цей допис лінком, якщо вам цікаво вивчити основні рухи і мати гарний майданчик для покращення навичок!

Далі в статті та двох наступних історіях я детально розповім (хоча, чесно кажучи, не дуже глибоко) про весь шлях від ідеї до розгортання: як я вибирав стек технологій, архітектуру та загальні можливості цього маленького додатка, розділені наступним чином:

  1. Визначення проєкту та цілей, технології та компроміси, архітектура фронтенду, перші результати прототипу.
  2. Створення повного MVP: проектування сутностей, повна інтеграція вправ та розділів, готовий прототип (з жахливим UI).
  3. Тестування, покращення UI / UX та адаптивність, розгортання додатка.

Цілі та опис проєкту:

Мета: створити безкоштовний веб-додаток для практики та вивчення команд Vim у цікавій та легкій формі.

Основна функціональність: інтерактивний текстовий редактор з структурованими вправами.

Можливості:

  1. Редагувати текст у веб-додатку.
  2. Вибір кількох вправ:
  3. Поділено на розділи.
  4. Кожна вправа повинна містити заголовок з інструкціями, пов’язані рухи, а також теги для позначення того, для чого призначена вправа.
  5. Можливість редагувати текст, поки він не досягне бажаного стану.
  6. Перезапуск вправи з початку.
  7. Отримати привітальне повідомлення та наступні кроки після завершення вправи.

Обмеження та зауваження:

  1. Це лише невеликий додаток з попередньо визначеними вправами та пісочницею для практики. Я не буду створювати жодної системи аутентифікації або відслідковувати конфіденційні дані користувачів, оскільки хочу створити додаток, який буде корисним для кожного. Однак, можливо, я додам рекламу та зроблю SEO, щоб збільшити органічний трафік і забезпечити окупність витраченого часу.
  2. Зберігання прогресу в локальному сховищі може бути хорошою ідеєю в майбутньому, але зараз це не є в рамках проєкту.
  3. Оскільки додаток буде лише кілька мегабайтів, немає сенсу будувати бекенд-систему, оскільки немає великої потреби в збереженні даних.

Повний стек технологій

Щоб бути повністю прозорим, ось повний список технологій, які я насправді використав для завершення цього проєкту. Хоча не все з цього потрібно, це все програмне забезпечення, яке я зазвичай використовую в особистих проєктах для оптимізації своїх робочих процесів.
pic

Фото: Алекс Найт на Unsplash

Перед тим, як перейти на Windows, я використовував Ubuntu 20.04. Після деякого часу роботи з Ubuntu я вирішив створити автоматизацію, щоб використати гнучкість середовища Linux для робочого столу. Хоча з часом я переглянув своє рішення працювати переважно в Linux через нижчу якість драйверів, я продовжував використовувати Linux-дистрибутиви для робочого столу ще близько двох років. Але повернемося до теми автоматизації.

Я почав з основних налаштувань клавіш за допомогою вбудованого інструменту для налаштування клавіатури в GNOME, але цього було занадто мало. У Linux-середовищі мені потрібно було щось більш потужне, тому я звернувся до сторонніх інструментів, таких як xmap та xkeymap. Вони корисні, але я знайшов їх трохи складними для функціональності, яку вони надають. Під час цього періоду я також використовував Windows на іншому диску для завдань, пов’язаних з Microsoft, і для ігор (dual boot). Під час програмування у Visual Studio мені прийшла ідея: «Давайте подивимося, як я можу переналаштувати свої клавіші на Windows», і саме тоді я відкрив AutoHotkey. Це відкрите програмне забезпечення, яке багатше на функції порівняно з інструментами, доступними на Linux. Воно також має GUI, вікно-шпигун, інструмент компіляції та багато іншого. З того часу я використовую свої власні скрипти AHK для різноманітних щоденних завдань.

Я почав серйозніше інтегрувати AHK у свою щоденну рутину, коли спробував використовувати гарячі клавіші Vim. Спочатку я вважав Vim неймовірно ефективним, але після тижня використання я отримав сильний біль у зап’ястях. Крім того, якщо б я звик до скорочень Vim, мені б довелося шукати розширення для використання Vim кожного разу, коли я переходив на іншу платформу. Це змусило мене шукати більш універсальне рішення.

Давайте подивимося, як я створив свою версію гарячих клавіш Vim в AHK. Використовувати стрілки під час кодування або набору тексту з правильною позицією рук на 10 пальців справді неефективно. Базові стандартні скорочення Vim виглядають так:

  • h — переміщення курсора вліво
  • j — переміщення курсора вниз
  • k — переміщення курсора вгору
  • l — переміщення курсора вправо
  • gj — переміщення курсора вниз (для багаторядкового тексту)
  • gk — переміщення курсора вгору (для багаторядкового тексту)
  • H — переміщення до верхньої частини екрану
  • M — переміщення до середини екрану
  • L — переміщення до нижньої частини екрану
  • w — стрибок вперед до початку слова
  • W — стрибок вперед до початку слова (слова можуть містити розділові знаки)

По-перше, для мене не має сенсу, коли стрілки розташовані в одну лінію на клавіатурі, і це відчувається як марна трата років розвинених рефлексів. Звичайно, ми можемо до цього звикнути, але зміни такого роду вимагають зміни мислення. Коли ти звикаєш до Vim, потім важко повернутися. Перш ніж поділитися своїми налаштуваннями клавіші, хочу зазначити, що я використовую турецьку QWERTY розкладку.

pic

Турецька Q клавіатура

“!” означає клавішу “Alt”. Я змінив свої клавіші на:

!j::Left  
!ı::Up  
!k::Down  
!l::Right  
!n::Home  
!m::End

Це базові налаштування клавіші. Тепер давайте подивимося, як я вибираю та маніпулюю текстом. Коли клавіша “Alt” натискається та утримується, ці скрипти надсилають стандартні команди натискання та відпускання клавіші залежно від того, які клавіші натискаються.
Велике пояснення від Вітора Брітто на Medium, як використовувати ці патерни в React можна знайти тут.

Маючи лише один вигляд (Головна сторінка), ми можемо мати три окремі контейнери для обробки бокової панелі зі списком вправ, контейнер для обробки поточного завдання, а також третій контейнер для текстового редактора.

React Context для управління станом:

Щоб уникнути потенційного дрилінгу пропсів і дуже легко ділитися та змінювати стан тексту та вправ, потрібно два різні провайдери.

Text Provider: для правильного поділу елементу тексту між редактором та поточною вправою можна використовувати простий Text Provider:

//providers/TextProvider.tsx  
import React, { createContext, useState } from "react";  

export const TextContext = createContext<{  
 text: string;  
 setText: React.Dispatch>;  
}>({ text: "", setText: () => {} });  
export function TextProvider({  
 children,  
}: React.PropsWithChildren): React.ReactElement {  
 const [text, setText] = useState("");  
 // ...  
 return (  

 {children}  

 );  
}

Current Exercise Provider: щоб можна було показувати заголовок, інструкції, бажаний стан тощо, а також безшовно перемикатися між різними вправами, провайдер currExercise легко може обробити поділ стану між цими компонентами.

// providers/CurrentExerciseProvider.tsx  
import { createContext, useState } from "react";  

// Примітка: цей тип Exercise буде власним класом пізніше.   
export type Exercise = {  
 id: number;  
 title: string;  
 instructions: string;  
 initialState: string;  
 desiredState: string;  
};  

export const CurrentExerciseContext = createContext<{  
 current: Exercise;  
 setCurrent: (Exercise: Exercise) => void;  
}>({  
 current: {  
 id: 0,  
 title: "",  
 instructions: "",  
 initialState: "",  
 desiredState: "",  
 },  
 setCurrent: () => {},  
});  

export function CurrentExerciseProvider({ children }: React.PropsWithChildren) {  
 const [current, setCurrent] = useState({  
 id: 0,  
 title: "",  
 instructions: "Замініть текст на бажаний стан за допомогою рухів vim",  
 initialState: "Це приклад",  
 desiredState: "Це бажаний стан",  
 });  
 // ...  
 return (  

 {children}  

 );  
}

Користувацькі хуки

Щоб абстрагувати деякі деталі реалізації редактора, на випадок, якщо потрібно буде зробити заміну, три різні функції можуть легко генерувати це розділення:

  • useCodeMirror : реальна реалізація codeMirror
  • useEditor — кастомний хук, в якому ми можемо підключити будь-яку реалізацію редактора, в цьому випадку codeMirror, але в майбутньому його можна буде легко замінити.
  • onUpdate — обробник для того, щоб сказати React, що робити при оновленні текстового редактора. У цьому випадку все, що нам потрібно — просто оновити стан тексту.
// hooks/useCodeMirror.tsx  
import { useRef, useState, useEffect, RefObject } from "react";  
import { EditorView, basicSetup } from "codemirror";  
import { vim } from "@replit/codemirror-vim";  
import { Extension } from "@codemirror/state";  

export default function useCodeMirror(extensions: Extension[]): {  
 ref: RefObject;  
 view: EditorView | undefined;  
} {  
 const ref = useRef(null);  
 const [view, setView] = useState();  
 useEffect(() => {  
 if (!ref.current) return;  
 const view = new EditorView({  
 extensions: [basicSetup, vim(), ...extensions],  
 parent: ref.current!,  
 });  
 setView(view);  
 /**  
 * Переконайтеся, що знищуєте екземпляр codemirror  
 * коли наші компоненти будуть демонтуватися.  

*/  
 return () => {  
 view.destroy();  
 setView(undefined);  
 };  
 /**  
 * Ми хочемо запустити цей ефект лише один раз, коли компонент монтується.  
 */  
 // eslint-disable-next-line react-hooks/exhaustive-deps  
 }, []);  
 return { ref, view };  
}  

// hooks/useEditor.tsx  
import { useEffect } from "react";  
import { onUpdate } from "src/hooks/onUpdate";  
import useCodeMirror from "src/hooks/useCodeMirror";  

interface CodeEditorProps {  
 value: string;  
 onChange: (value: string) => void;  
}  

export function useCodeEditor({  
 value,  
 onChange,  
}: CodeEditorProps): React.RefObject {  
 const { ref, view } = useCodeMirror([onUpdate(onChange)]);  
 useEffect(() => {  
 if (view) {  
 const editorValue = view.state.doc.toString();  
 if (value !== editorValue) {  
 view.dispatch({  
 changes: {  
 from: 0,  
 to: editorValue.length,  
 insert: value || "",  
 },  
 });  
 }  
 }  
 }, [value, view]);  
 return ref;  
}  
// hooks/onUpdate.tsx  
import { EditorView, ViewUpdate } from "@codemirror/view";  
import { Extension } from "@codemirror/state";  
type OnChange = (value: string, viewUpdate: ViewUpdate) => void;  
export function onUpdate(onChange: OnChange): Extension {  
 return EditorView.updateListener.of((viewUpdate: ViewUpdate) => {  
 if (viewUpdate.docChanged) {  
 const doc = viewUpdate.state.doc;  
 const value = doc.toString();  
 onChange(value, viewUpdate);  
 }  
 });  
}

onUpdate все ще занадто тісно пов'язаний з mirrorCode на мою думку, але я не вважаю, що це є великою проблемою на даний момент.

З цими трьома хуками, використання стає досить простим:

// приклад використання контейнера редактора  
import { useContext } from "react";  
import { useCodeEditor } from "src/hooks/useCodeEditor";  
import { TextContext } from "src/providers/TextProvider";  

export default function TextEditorContainer() {  
 const { text, setText } = useContext(TextContext);  
 const ref = useCodeEditor({ value: text, onChange: setText });  
 return 
;   } ```  Я повністю базував свою реалізацію на цій [класній статті](https://www.codiga.io/blog/revisiting-codemirror-6-react-implementation/) від Codiga. Дякую за поділ, Оскар!  **Огляд архітектури на високому рівні**  З усім тим кодом в стороні, давайте зробимо крок назад і подивимося на фінальну структуру FE додатку:  ![pic](https://drive.javascript.org.ua/ef28d0c7201_7Wpw6O3mI3oLZEvLPXwFRg_png)  _Поганий UI повідомлення про привітання після завершення вправи._  Попереду ще довгий шлях. Але суть є, потрібно просто кілька десятків годин роботи і це буде гарний додаток, де люди можуть грати та вчитися vim без застрягання в терміналі.  Ось і все, частина 1 будівництва додатку для навчання Vim. Залишайтеся на зв'язку для частини 2!



Перекладено з: [Building a Vim Learning Web Application](https://medium.com/@alecbarba/building-a-vim-learning-web-application-640e550f8d0d)