forked from mirror/codesnap.nvim
parent
f8773e538e
commit
383be3a533
|
@ -1,15 +1,17 @@
|
||||||
local logger = require("codesnap.utils.logger")
|
local logger = require("codesnap.utils.logger")
|
||||||
local path_utils = require("codesnap.utils.path")
|
local static = require("codesnap.static")
|
||||||
|
|
||||||
local client = {
|
local client = {
|
||||||
job_id = 0,
|
job_id = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local cwd = static.cwd .. "/snap-server"
|
||||||
|
|
||||||
function client:connect()
|
function client:connect()
|
||||||
return vim.fn.jobstart({
|
return vim.fn.jobstart({
|
||||||
path_utils.back(path_utils.back(debug.getinfo(1, "S").source:sub(2):match("(.*[/\\])")))
|
cwd .. "/target/release/snap-server",
|
||||||
.. "/snap-server/target/debug/snap-server",
|
|
||||||
}, {
|
}, {
|
||||||
|
cwd = cwd,
|
||||||
stderr_buffered = true,
|
stderr_buffered = true,
|
||||||
rpc = true,
|
rpc = true,
|
||||||
on_stderr = function(_, err)
|
on_stderr = function(_, err)
|
||||||
|
|
|
@ -3,15 +3,14 @@ local static = require("codesnap.static")
|
||||||
local client = require("codesnap.client")
|
local client = require("codesnap.client")
|
||||||
local visual_utils = require("codesnap.utils.visual")
|
local visual_utils = require("codesnap.utils.visual")
|
||||||
|
|
||||||
local main = {}
|
local main = {
|
||||||
|
cwd = static.cwd,
|
||||||
|
preview_switch = static.preview_switch,
|
||||||
|
}
|
||||||
|
|
||||||
function main.setup(config)
|
function main.setup(config)
|
||||||
static.config = table_utils.merge(static.config, config == nil and {} or config)
|
static.config = table_utils.merge(static.config, config == nil and {} or config)
|
||||||
|
|
||||||
print(vim.inspect(static.config))
|
|
||||||
print(table_utils.serialize_json(static.config))
|
|
||||||
print()
|
|
||||||
|
|
||||||
if static.config.auto_load then
|
if static.config.auto_load then
|
||||||
client:start()
|
client:start()
|
||||||
end
|
end
|
||||||
|
@ -23,4 +22,12 @@ function main.preview_code()
|
||||||
client:send("preview_code", { content = visual_utils.get_selected_text(), language = vim.bo.filetype })
|
client:send("preview_code", { content = visual_utils.get_selected_text(), language = vim.bo.filetype })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function main.open_preview()
|
||||||
|
client:send("open_preview")
|
||||||
|
end
|
||||||
|
|
||||||
|
function main.stop_client()
|
||||||
|
client:stop()
|
||||||
|
end
|
||||||
|
|
||||||
return main
|
return main
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
local path_utils = require("codesnap.utils.path")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config = {
|
config = {
|
||||||
breadcrumbs = true,
|
|
||||||
column_number = true,
|
|
||||||
mac_window_bar = true,
|
mac_window_bar = true,
|
||||||
opacity = true,
|
opacity = true,
|
||||||
watermark = "CodeSnap.nvim",
|
watermark = "CodeSnap.nvim",
|
||||||
auto_load = true,
|
auto_load = true,
|
||||||
},
|
},
|
||||||
|
cwd = path_utils.back(path_utils.back(debug.getinfo(1, "S").source:sub(2):match("(.*[/\\])"))),
|
||||||
preview_switch = true,
|
preview_switch = true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
local codesnap = require("codesnap")
|
||||||
|
local snap_client_cwd = codesnap.cwd .. "/snap-client"
|
||||||
|
local snap_server_cwd = codesnap.cwd .. "/snap-server"
|
||||||
|
|
||||||
|
-- Build preview client
|
||||||
|
os.execute(
|
||||||
|
"cd "
|
||||||
|
.. snap_client_cwd
|
||||||
|
.. " && "
|
||||||
|
.. "npm i "
|
||||||
|
.. " && "
|
||||||
|
.. "npm run build"
|
||||||
|
.. " && "
|
||||||
|
.. "mv ./build "
|
||||||
|
.. snap_server_cwd
|
||||||
|
.. "/public"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Build server
|
||||||
|
os.execute("cd " .. snap_server_cwd .. " && " .. "cargo build --release")
|
|
@ -1,19 +1,19 @@
|
||||||
local codesnap = require("codesnap")
|
local codesnap = require("codesnap")
|
||||||
local static = require("codesnap.static")
|
-- local client = require("codesnap.client")
|
||||||
local client = require("codesnap.client")
|
|
||||||
|
|
||||||
-- snap code
|
-- vim.api.nvim_create_user_command("CodeSnap", function()
|
||||||
vim.api.nvim_create_user_command("CodeSnap", function() end, {})
|
-- client:send("copy")
|
||||||
|
-- end, {})
|
||||||
|
|
||||||
vim.api.nvim_create_user_command("CodeSnapPreviewOn", function() end, {})
|
vim.api.nvim_create_user_command("CodeSnapPreviewOn", function()
|
||||||
|
codesnap.open_preview()
|
||||||
vim.api.nvim_create_user_command("CodeSnapPreviewOff", function() end, {})
|
end, {})
|
||||||
|
|
||||||
vim.api.nvim_create_autocmd({ "CursorMoved" }, {
|
vim.api.nvim_create_autocmd({ "CursorMoved" }, {
|
||||||
callback = function()
|
callback = function()
|
||||||
local mode = vim.api.nvim_get_mode().mode
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
|
||||||
if mode ~= "v" or not static.preview_switch then
|
if mode ~= "v" or not codesnap.preview_switch then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -24,6 +24,6 @@ vim.api.nvim_create_autocmd({ "CursorMoved" }, {
|
||||||
vim.api.nvim_create_autocmd({ "VimLeavePre" }, {
|
vim.api.nvim_create_autocmd({ "VimLeavePre" }, {
|
||||||
pattern = "*",
|
pattern = "*",
|
||||||
callback = function()
|
callback = function()
|
||||||
client:stop()
|
codesnap.stop_client()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,34 +1,37 @@
|
||||||
import React, {
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import useWebSocket, { ReadyState } from "react-use-websocket";
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
||||||
import { ControlBar, Editor, Frame, Panel } from "./components";
|
import { ControlBar, Editor, Frame, Panel } from "./components";
|
||||||
import { useConfig, useEvent } from "./hooks";
|
import { useConfig, useEvent } from "./hooks";
|
||||||
import { toPng, toJpeg, toBlob, toPixelData, toSvg } from "html-to-image";
|
import { toPng, toBlob } from "html-to-image";
|
||||||
import download from "downloadjs";
|
import download from "downloadjs";
|
||||||
|
|
||||||
const CODE_EMPTY_PLACEHOLDER = `print "Hello, CodeSnap.nvim!"`;
|
const CODE_EMPTY_PLACEHOLDER = `print "Hello, CodeSnap.nvim!"`;
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [socketUrl] = useState("ws://127.0.0.1:8080/ws");
|
const [socketUrl] = useState(`ws://${window.location.host}/ws`);
|
||||||
const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl);
|
const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl);
|
||||||
const event = useEvent(lastMessage);
|
const event = useEvent(lastMessage);
|
||||||
const config = useConfig(event?.config_setup);
|
const config = useConfig(event?.config_setup);
|
||||||
const frameRef = useRef<HTMLDivElement | null>(null);
|
const frameRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [isCopyButtonDisabled, setIsCopyButtonDisabled] = useState(false);
|
||||||
|
|
||||||
const handleCopyButtonClick = useCallback(async () => {
|
const handleCopyButtonClick = useCallback(async () => {
|
||||||
if (!frameRef.current) {
|
if (!frameRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsCopyButtonDisabled(true);
|
||||||
|
|
||||||
|
try {
|
||||||
const blob = await toBlob(frameRef.current);
|
const blob = await toBlob(frameRef.current);
|
||||||
const clipboardItem = new ClipboardItem({ "image/png": blob! });
|
const clipboardItem = new ClipboardItem({ "image/png": blob! });
|
||||||
|
|
||||||
navigator.clipboard.write([clipboardItem]);
|
navigator.clipboard.write([clipboardItem]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
setIsCopyButtonDisabled(false);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleExportClick = useCallback(async () => {
|
const handleExportClick = useCallback(async () => {
|
||||||
|
@ -41,6 +44,24 @@ function App() {
|
||||||
download(dataURL, "codesnap.png");
|
download(dataURL, "codesnap.png");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const notifyCopyCommand = useCallback(async () => {
|
||||||
|
if (!frameRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataURL = await toPng(frameRef.current);
|
||||||
|
|
||||||
|
sendMessage(dataURL);
|
||||||
|
}, [sendMessage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (readyState !== ReadyState.OPEN || !event?.copy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyCopyCommand();
|
||||||
|
}, [event, readyState, notifyCopyCommand]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex flex-col items-center bg-deep-gray">
|
<div className="w-full h-full flex flex-col items-center bg-deep-gray">
|
||||||
<p className="rainbow-text text-4xl font-extrabold mt-20">
|
<p className="rainbow-text text-4xl font-extrabold mt-20">
|
||||||
|
@ -48,11 +69,12 @@ function App() {
|
||||||
</p>
|
</p>
|
||||||
<Panel>
|
<Panel>
|
||||||
<ControlBar
|
<ControlBar
|
||||||
|
isCopyButtonDisabled={isCopyButtonDisabled}
|
||||||
onExportClick={handleExportClick}
|
onExportClick={handleExportClick}
|
||||||
onCopyClick={handleCopyButtonClick}
|
onCopyClick={handleCopyButtonClick}
|
||||||
readyState={readyState}
|
readyState={readyState}
|
||||||
/>
|
/>
|
||||||
<div className="rounded-xl overflow-hidden">
|
<div id="frame" className="rounded-xl overflow-hidden">
|
||||||
<Frame ref={frameRef} watermark={config?.watermark}>
|
<Frame ref={frameRef} watermark={config?.watermark}>
|
||||||
<Editor
|
<Editor
|
||||||
language={event?.code?.language}
|
language={event?.code?.language}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { ReadyState } from "react-use-websocket";
|
||||||
|
|
||||||
|
interface ConnectionStatusProps {
|
||||||
|
readyState: ReadyState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONNECTION_STATUS_MAP = {
|
||||||
|
[ReadyState.CONNECTING]: {
|
||||||
|
text: "Connecting",
|
||||||
|
color: "#fdcb6e",
|
||||||
|
},
|
||||||
|
[ReadyState.OPEN]: {
|
||||||
|
text: "Connected",
|
||||||
|
color: "#00b894",
|
||||||
|
},
|
||||||
|
[ReadyState.CLOSING]: {
|
||||||
|
text: "Closing",
|
||||||
|
color: "#fab1a0",
|
||||||
|
},
|
||||||
|
[ReadyState.CLOSED]: {
|
||||||
|
text: "Closed",
|
||||||
|
color: "#636e72",
|
||||||
|
},
|
||||||
|
[ReadyState.UNINSTANTIATED]: {
|
||||||
|
text: "Uninstantiated",
|
||||||
|
color: "#2d3436",
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const UNKNOWN_STATE = { text: "Unknown", color: "#a29bfe" };
|
||||||
|
|
||||||
|
export const ConnectionStatus = ({ readyState }: ConnectionStatusProps) => {
|
||||||
|
const parsedState = useMemo(
|
||||||
|
() => CONNECTION_STATUS_MAP[readyState] ?? UNKNOWN_STATE,
|
||||||
|
[readyState],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-grow flex-row items-center mr-1 ml-2">
|
||||||
|
<div
|
||||||
|
className="flex w-5 h-5 mr-2 justify-center items-center rounded-full"
|
||||||
|
style={{ backgroundColor: `${parsedState.color}50` }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
style={{ backgroundColor: parsedState.color }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
{parsedState.text}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -2,6 +2,7 @@ import { ConnectionStatus } from "./connection-status";
|
||||||
import { ReadyState } from "react-use-websocket";
|
import { ReadyState } from "react-use-websocket";
|
||||||
|
|
||||||
interface ControlBarProps {
|
interface ControlBarProps {
|
||||||
|
isCopyButtonDisabled: boolean;
|
||||||
onCopyClick(): void;
|
onCopyClick(): void;
|
||||||
onExportClick(): void;
|
onExportClick(): void;
|
||||||
readyState: ReadyState;
|
readyState: ReadyState;
|
||||||
|
@ -10,13 +11,11 @@ interface ControlBarProps {
|
||||||
export const ControlBar = ({
|
export const ControlBar = ({
|
||||||
onCopyClick,
|
onCopyClick,
|
||||||
onExportClick,
|
onExportClick,
|
||||||
|
isCopyButtonDisabled,
|
||||||
readyState,
|
readyState,
|
||||||
}: ControlBarProps) => {
|
}: ControlBarProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="bg-neutral rounded-xl mb-2 p-1 flex flex-row items-center">
|
||||||
className="bg-neutral rounded-xl mb-2 p-1 flex flex-row items-center"
|
|
||||||
onClick={onCopyClick}
|
|
||||||
>
|
|
||||||
<ConnectionStatus readyState={readyState} />
|
<ConnectionStatus readyState={readyState} />
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{/*
|
{/*
|
||||||
|
@ -44,7 +43,11 @@ export const ControlBar = ({
|
||||||
<path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z" />
|
<path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button className="btn">
|
<button
|
||||||
|
onClick={onCopyClick}
|
||||||
|
id="copy"
|
||||||
|
className={`btn ${isCopyButtonDisabled && "btn-disabled"}`}
|
||||||
|
>
|
||||||
Copy
|
Copy
|
||||||
<svg
|
<svg
|
||||||
className="fill-neutral-content"
|
className="fill-neutral-content"
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { useLocalStorage } from "./use-storage";
|
import { useLocalStorage } from "./use-storage";
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
breadcrumbs: boolean;
|
|
||||||
column_number: boolean;
|
|
||||||
mac_window_bar: boolean;
|
mac_window_bar: boolean;
|
||||||
opacity: boolean;
|
opacity: boolean;
|
||||||
watermark: string;
|
watermark: string;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { Config } from "./use-config";
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
CONFIG_SETUP = "config_setup",
|
CONFIG_SETUP = "config_setup",
|
||||||
CODE = "code",
|
CODE = "code",
|
||||||
|
COPY = "copy",
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeMessage = {
|
type CodeMessage = {
|
||||||
|
@ -14,6 +15,7 @@ type CodeMessage = {
|
||||||
type ParsedConfig = {
|
type ParsedConfig = {
|
||||||
[EventType.CODE]: CodeMessage;
|
[EventType.CODE]: CodeMessage;
|
||||||
[EventType.CONFIG_SETUP]: Config;
|
[EventType.CONFIG_SETUP]: Config;
|
||||||
|
[EventType.COPY]: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useEvent = (
|
export const useEvent = (
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,13 +3,17 @@ name = "snap-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.13.3"
|
actix = "0.13.3"
|
||||||
|
actix-files = "0.6.5"
|
||||||
actix-web = "4.5.1"
|
actix-web = "4.5.1"
|
||||||
actix-web-actors = "4.3.0"
|
actix-web-actors = "4.3.0"
|
||||||
|
arboard = "3.3.1"
|
||||||
|
headless_chrome = "1.0.9"
|
||||||
|
image = "0.24.8"
|
||||||
|
image-base64 = "0.1.0"
|
||||||
neovim-lib = "0.6.1"
|
neovim-lib = "0.6.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde = {version = "1.0.196", features = ["derive", "serde_derive", "std"]}
|
serde = {version = "1.0.196", features = ["derive", "serde_derive", "std"]}
|
||||||
serde_json = "1.0.113"
|
serde_json = "1.0.113"
|
||||||
|
webbrowser = "0.8.12"
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
use arboard::Clipboard;
|
||||||
|
use arboard::ImageData;
|
||||||
|
use image::load_from_memory;
|
||||||
|
|
||||||
|
pub fn copy_base64_image_into_clipboard(base64_image: String) {
|
||||||
|
copy_memory_image_into_clipboard(&image_base64::from_base64(base64_image))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_memory_image_into_clipboard(buffer: &Vec<u8>) {
|
||||||
|
let dynamic_image = load_from_memory(buffer).unwrap();
|
||||||
|
|
||||||
|
let image = ImageData {
|
||||||
|
width: dynamic_image.width() as usize,
|
||||||
|
height: dynamic_image.height() as usize,
|
||||||
|
bytes: dynamic_image.as_bytes().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut clipboard = Clipboard::new().unwrap();
|
||||||
|
|
||||||
|
clipboard.set_image(image).unwrap();
|
||||||
|
}
|
|
@ -3,15 +3,15 @@ pub mod config;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod neovim;
|
pub mod neovim;
|
||||||
|
|
||||||
use actix::{Actor, Addr, AsyncContext, Context};
|
use actix::{Actor, Addr, Context};
|
||||||
use arguments::parse_string_first;
|
use arguments::parse_string_first;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
use headless_chrome::{protocol::cdp::Page, Browser, Tab};
|
||||||
pub use messages::Message;
|
pub use messages::Message;
|
||||||
use neovim::Neovim;
|
use neovim::Neovim;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
error::Error,
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ use crate::{
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
neovim: Arc<Mutex<Neovim>>,
|
neovim: Arc<Mutex<Neovim>>,
|
||||||
server: Arc<Addr<Server>>,
|
server: Arc<Addr<Server>>,
|
||||||
|
port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for EventHandler {
|
impl Actor for EventHandler {
|
||||||
|
@ -34,17 +35,47 @@ impl Actor for EventHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
pub fn new(neovim: Arc<Mutex<Neovim>>, server: Arc<Addr<Server>>) -> EventHandler {
|
pub fn new(neovim: Arc<Mutex<Neovim>>, server: Arc<Addr<Server>>, port: u16) -> EventHandler {
|
||||||
EventHandler { neovim, server }
|
EventHandler {
|
||||||
|
neovim,
|
||||||
|
server,
|
||||||
|
port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_browser(&self) -> Result<Arc<Tab>, Box<dyn Error>> {
|
||||||
|
let browser = Browser::default()?;
|
||||||
|
|
||||||
|
let tab = browser.new_tab()?;
|
||||||
|
|
||||||
|
tab.navigate_to(format!("http://localhost:{}", self.port).as_str())?;
|
||||||
|
|
||||||
|
Ok(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_listen(&mut self) {
|
pub fn start_listen(&mut self) {
|
||||||
let receiver = self.neovim.lock().unwrap().create_receiver();
|
let receiver = self.neovim.lock().unwrap().create_receiver();
|
||||||
|
|
||||||
for (event_name, values) in receiver {
|
for (event_name, values) in receiver {
|
||||||
self.neovim.lock().unwrap().print(&event_name);
|
// self.neovim.lock().unwrap().print(&event_name);
|
||||||
|
|
||||||
match Message::from(event_name.clone()) {
|
match Message::from(event_name.clone()) {
|
||||||
|
Message::OpenPreview => {
|
||||||
|
let _ = webbrowser::open(format!("http://localhost:{}", self.port).as_str());
|
||||||
|
}
|
||||||
|
Message::Copy => {
|
||||||
|
// let _png_data = tab
|
||||||
|
// .wait_for_element("#frame")
|
||||||
|
// .unwrap()
|
||||||
|
// .capture_screenshot(Page::CaptureScreenshotFormatOption::Png)
|
||||||
|
// .unwrap();
|
||||||
|
//
|
||||||
|
// std::fs::write("hello_world.png", _png_data).unwrap();
|
||||||
|
|
||||||
|
// self.server.do_send(ClientMessage {
|
||||||
|
// msg: Event::new("copy", json!("{}")).into(),
|
||||||
|
// })
|
||||||
|
}
|
||||||
Message::PreviewCode => self.server.do_send(ClientMessage {
|
Message::PreviewCode => self.server.do_send(ClientMessage {
|
||||||
msg: Event::new(
|
msg: Event::new(
|
||||||
"code",
|
"code",
|
||||||
|
|
|
@ -2,8 +2,6 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Deserialize, Clone)]
|
#[derive(Serialize, Debug, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
breadcrumbs: bool,
|
|
||||||
column_number: bool,
|
|
||||||
mac_window_bar: bool,
|
mac_window_bar: bool,
|
||||||
opacity: bool,
|
opacity: bool,
|
||||||
watermark: Option<String>,
|
watermark: Option<String>,
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
PreviewCode,
|
PreviewCode,
|
||||||
ConfigSetup,
|
ConfigSetup,
|
||||||
|
Copy,
|
||||||
|
OpenPreview,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +12,8 @@ impl Eq for Message {}
|
||||||
impl From<String> for Message {
|
impl From<String> for Message {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
match value.as_str() {
|
match value.as_str() {
|
||||||
|
"open_preview" => Message::OpenPreview,
|
||||||
|
"copy" => Message::Copy,
|
||||||
"preview_code" => Message::PreviewCode,
|
"preview_code" => Message::PreviewCode,
|
||||||
"config_setup" => Message::ConfigSetup,
|
"config_setup" => Message::ConfigSetup,
|
||||||
_ => Message::Unknown,
|
_ => Message::Unknown,
|
||||||
|
|
|
@ -1,18 +1,27 @@
|
||||||
|
mod clipboard;
|
||||||
mod event;
|
mod event;
|
||||||
mod event_handler;
|
mod event_handler;
|
||||||
|
mod port;
|
||||||
mod server;
|
mod server;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{
|
||||||
|
net::TcpListener,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use actix::{Actor, Addr, Arbiter, StreamHandler};
|
use actix::{Actor, Addr, Arbiter};
|
||||||
|
use actix_files::{Files, NamedFile};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
web::{self, Data, Payload},
|
web::{self, Data, Payload},
|
||||||
App, Error, HttpRequest, HttpResponse, HttpServer,
|
App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||||
};
|
};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
use event_handler::{neovim::Neovim, Config};
|
use clipboard::copy_memory_image_into_clipboard;
|
||||||
use event_handler::{EventHandler, Message};
|
use event_handler::neovim::Neovim;
|
||||||
|
use event_handler::EventHandler;
|
||||||
|
use headless_chrome::{protocol::cdp::Page, Browser, Tab};
|
||||||
|
use port::get_available_port;
|
||||||
use server::Server;
|
use server::Server;
|
||||||
use session::Session;
|
use session::Session;
|
||||||
|
|
||||||
|
@ -24,40 +33,33 @@ async fn index(
|
||||||
ws::start(Session::new(server), &req, stream)
|
ws::start(Session::new(server), &req, stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn root() -> impl Responder {
|
||||||
|
NamedFile::open_async("./public/index.html").await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let neovim = Arc::new(Mutex::new(Neovim::new()));
|
let neovim = Arc::new(Mutex::new(Neovim::new()));
|
||||||
let server = Arc::new(Server::new(neovim.clone()).start());
|
let server = Arc::new(Server::new(neovim.clone()).start());
|
||||||
let cloned_server = Arc::clone(&server);
|
let cloned_server = Arc::clone(&server);
|
||||||
|
let cloned_neovim = neovim.clone();
|
||||||
|
let available_port = get_available_port().unwrap();
|
||||||
|
|
||||||
Arbiter::new().spawn(async {
|
Arbiter::new().spawn(async move {
|
||||||
EventHandler::new(neovim, cloned_server).start();
|
EventHandler::new(cloned_neovim.clone(), cloned_server.clone(), available_port).start();
|
||||||
});
|
});
|
||||||
|
|
||||||
HttpServer::new(move || {
|
let _ = HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(server.clone()))
|
.app_data(web::Data::new(server.clone()))
|
||||||
.route("/ws", web::get().to(index))
|
.route("/ws", web::get().to(index))
|
||||||
|
.service(web::resource("/").to(root))
|
||||||
|
.service(Files::new("/public", "./public"))
|
||||||
|
.service(Files::new("/static", "./public/static"))
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8080))?
|
.bind(("127.0.0.1", available_port))?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await;
|
||||||
}
|
|
||||||
|
|
||||||
// fn main() {
|
Ok(())
|
||||||
// let data = r#"
|
}
|
||||||
// {
|
|
||||||
// "breadcrumbs":true,
|
|
||||||
// "watermark":"CodeSnap.nvim",
|
|
||||||
// "mac_window_bar":true,
|
|
||||||
// "column_number":true,
|
|
||||||
// "auto_load":true,
|
|
||||||
// "background":{
|
|
||||||
// "grandient":true
|
|
||||||
// }
|
|
||||||
// }"#;
|
|
||||||
//
|
|
||||||
// let config: Config = serde_json::from_str(data).unwrap();
|
|
||||||
//
|
|
||||||
// println!("{:?}", config)
|
|
||||||
// }
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
use std::net::TcpListener;
|
||||||
|
|
||||||
|
pub fn get_available_port() -> Option<u16> {
|
||||||
|
(8000..10000).find(|port| port_is_available(*port))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn port_is_available(port: u16) -> bool {
|
||||||
|
match TcpListener::bind(("127.0.0.1", port)) {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::server::{ClientMessage, Connect, Disconnect, Server, ServerMessage};
|
use crate::server::{Connect, Disconnect, Server, ServerMessage};
|
||||||
use actix::{
|
use actix::{
|
||||||
dev::ContextFutureSpawner, fut, Actor, ActorContext, ActorFutureExt, Addr, AsyncContext,
|
dev::ContextFutureSpawner, fut, Actor, ActorContext, ActorFutureExt, Addr, AsyncContext,
|
||||||
Handler, Running, StreamHandler, WrapFuture,
|
Handler, Running, StreamHandler, WrapFuture,
|
||||||
|
@ -13,6 +13,10 @@ use std::{
|
||||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
const CLIENT_TIMEOUT: Duration = Duration::from_secs(30);
|
const CLIENT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
|
fn is_valid_base64_image(base64_image: &str) -> bool {
|
||||||
|
base64_image.starts_with("data:image/png;base64")
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
id: usize,
|
id: usize,
|
||||||
server: Arc<Addr<Server>>,
|
server: Arc<Addr<Server>>,
|
||||||
|
@ -30,7 +34,6 @@ impl Session {
|
||||||
|
|
||||||
fn heartbeat(&self, ctx: &mut ws::WebsocketContext<Self>) {
|
fn heartbeat(&self, ctx: &mut ws::WebsocketContext<Self>) {
|
||||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||||
// check client heartbeats
|
|
||||||
if Instant::now().duration_since(act.heartbeat) > CLIENT_TIMEOUT {
|
if Instant::now().duration_since(act.heartbeat) > CLIENT_TIMEOUT {
|
||||||
act.server.do_send(Disconnect { id: act.id });
|
act.server.do_send(Disconnect { id: act.id });
|
||||||
|
|
||||||
|
@ -89,9 +92,13 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for Session {
|
||||||
self.heartbeat = Instant::now();
|
self.heartbeat = Instant::now();
|
||||||
}
|
}
|
||||||
Ok(ws::Message::Text(text)) => {
|
Ok(ws::Message::Text(text)) => {
|
||||||
self.server.do_send(ClientMessage {
|
// let image = text.to_string();
|
||||||
msg: "aaaaa".to_string(),
|
|
||||||
});
|
// if !is_valid_base64_image(&image) {
|
||||||
|
ctx.text(text)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// copy_base64_image_into_clipboard(image);
|
||||||
}
|
}
|
||||||
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
|
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
Loading…
Reference in New Issue