- A dynamic environment, rapid decision-making, and a deep love for startups are what fuel my professional passion
- Worked as a sysadmin for 3 years - I have steel nerves and poker face)
Software Engineer at Blinkin.io
~10yrs of experience
- 2-5 hours calls with C-level
- analyzing similar products (engineering blogs, articles)
- drawing schemas/prototypes (Miro)
- Functional and Non-functional requirements
# CHAPTER 1
# CHAPTER 2
Node-based Editor
Realtime Collaboration
Drag&Drop & Resize & Rotate
MultiSelection & Grouping
Powerful inline Text/Image Editor
Themes & Templates
Tenant-based architecture
Data governence
Preview inside Editor
Publish
Ctrl + Z
History
ChatGPT / AI / ML
Forms
Integrations
HTML vs Canvas
PDF -> Guide
# CHAPTER 2
# CHAPTER 3
# CHAPTER 3
Architectural Decision Record
# CHAPTER 4
# CHAPTER 5
Feature-Sliced Design + Atomic Design
# CHAPTER 5
# CHAPTER 5
# CHAPTER 6
Incremental change update
# CHAPTER 7
# CHAPTER 7
- Caching
- Updating "out of date" data in the background
- Managing memory - revalidate, refetch by Query Keys
- Make your application more maintainable
- Potentially help you save on bandwidth
# CHAPTER 7
const useFonts = useQuery({
queryKey: [QUERY_KEYS.FONTS],
queryFn: async () => FontsAPI.fetchFonts().then(d => data.data)
});
const { data, isLoading } = useFonts();
....
export const useAddNewFontMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (payload: AddNewFontRequest) => FontsAPI.addNewFont(payload),
onSuccess: () => queryClient.invalidateQueries([
QUERY_KEYS.FONTS
]);
});
}
# CHAPTER 8
# CHAPTER 10
An extensible text editor framework
# CHAPTER 11
for building node-based editors
# CHAPTER 12
Shared editing framework
# CHAPTER 12
Conflict-free
Replicated
Data
Types
=
W
T
F
# CHAPTER 12
import * as Y from 'yjs';
const ydoc = new Y.Doc();
const nodesMap = ydoc.getMap<Node>('nodes');
function useNodesStateSynced(): [Node[], OnNodesChange] {
const [nodes, setNodes] = useState<Node[]>([]);
const onNodesChanges = useCallback((changes: NodeChange[]) => {});
useEffect(() => {
const observer = () => setNodes(Array.from(nodesMap.values()));
setNodes(Array.from(nodesMap.values()));
nodesMap.observe(observer);
return () => nodesMap.unobserve(observer);
}, [setNodes]);
return [nodes, onNodesChanges];
}
# CHAPTER 12
# CHAPTER 13
Tree-shakeable & Platform-agnostic
Smart Anchor Positioning
# CHAPTER 13
const { refs, update, floatingStyles } = useFloating({
placement: placement ?? 'top',
open: opened,
onOpenChange: setOpened,
middleware: [
flip({
fallbackAxisSideDirection: 'end',
fallbackStrategy: 'bestFit',
fallbackPlacements: ['top', 'bottom', 'left', 'right'],
}),
offset(15)
],
});
# CHAPTER 14
# CHAPTER 14
# CHAPTER 13
# CHAPTER 13
const data = {
components: [{
"id": "98268bd4-568c-4a5a-8751-109e6ff05ef8",
"data": {
"media": { id: "..", path: "https://example.com/photos/2280547.jpeg" },
"order": 0,
},
...
}],
};
// Convert all medias to Base64
toBase64(data.components.media);
navigator.clipboard.writeText(createClipboardDataFromJSON(data));
// {
// type: "data/clipboard",
// payload: { components: [...] }
// }
# CHAPTER 15
"Simplicity is not the state of when there is nothing more to add, but the state when there is nothing more to take away."
(с)Antoine de Saint-Exupéry