done graphics basic
This commit is contained in:
@@ -9,6 +9,8 @@ configure_file(
|
|||||||
|
|
||||||
add_library(pdfengine STATIC
|
add_library(pdfengine STATIC
|
||||||
src/core/engine_info.cpp
|
src/core/engine_info.cpp
|
||||||
|
src/core/graphics_state.cpp
|
||||||
|
src/core/display_list.cpp
|
||||||
src/parser/pdfium_loader.cpp
|
src/parser/pdfium_loader.cpp
|
||||||
src/fonts/font_face.cpp
|
src/fonts/font_face.cpp
|
||||||
src/fonts/hb_shaper.cpp
|
src/fonts/hb_shaper.cpp
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <pdfengine/graphics_state.hpp>
|
||||||
|
|
||||||
|
namespace pdfengine {
|
||||||
|
|
||||||
|
class CommandVisitor;
|
||||||
|
|
||||||
|
// Base class for all drawing commands
|
||||||
|
struct Command {
|
||||||
|
virtual ~Command() = default;
|
||||||
|
virtual void accept(CommandVisitor& visitor) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Specific Command Types ---
|
||||||
|
|
||||||
|
struct SaveStateCommand : public Command {
|
||||||
|
void accept(CommandVisitor& visitor) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RestoreStateCommand : public Command {
|
||||||
|
void accept(CommandVisitor& visitor) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetTransformCommand : public Command {
|
||||||
|
Matrix matrix;
|
||||||
|
explicit SetTransformCommand(const Matrix& m) : matrix(m) {}
|
||||||
|
void accept(CommandVisitor& visitor) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FillRectCommand : public Command {
|
||||||
|
float x, y, width, height;
|
||||||
|
FillRectCommand(float x, float y, float w, float h) : x(x), y(y), width(w), height(h) {}
|
||||||
|
void accept(CommandVisitor& visitor) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DrawTextCommand : public Command {
|
||||||
|
std::string text;
|
||||||
|
float x, y;
|
||||||
|
// We would eventually have a font reference here too
|
||||||
|
DrawTextCommand(std::string text, float x, float y) : text(std::move(text)), x(x), y(y) {}
|
||||||
|
void accept(CommandVisitor& visitor) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Visitor Interface ---
|
||||||
|
|
||||||
|
// The visitor interface that the renderer (or replay engine) implements
|
||||||
|
class CommandVisitor {
|
||||||
|
public:
|
||||||
|
virtual ~CommandVisitor() = default;
|
||||||
|
virtual void visit(const SaveStateCommand& cmd) = 0;
|
||||||
|
virtual void visit(const RestoreStateCommand& cmd) = 0;
|
||||||
|
virtual void visit(const SetTransformCommand& cmd) = 0;
|
||||||
|
virtual void visit(const FillRectCommand& cmd) = 0;
|
||||||
|
virtual void visit(const DrawTextCommand& cmd) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Display List Container ---
|
||||||
|
|
||||||
|
// A container that stores a sequence of drawing commands.
|
||||||
|
class DisplayList {
|
||||||
|
public:
|
||||||
|
DisplayList() = default;
|
||||||
|
|
||||||
|
// Add commands directly
|
||||||
|
void addCommand(std::unique_ptr<Command> cmd);
|
||||||
|
|
||||||
|
// Replay the commands to a visitor (renderer)
|
||||||
|
void replay(CommandVisitor& visitor) const;
|
||||||
|
|
||||||
|
// Helper methods to easily append common commands
|
||||||
|
void saveState();
|
||||||
|
void restoreState();
|
||||||
|
void setTransform(const Matrix& m);
|
||||||
|
void fillRect(float x, float y, float w, float h);
|
||||||
|
void drawText(const std::string& text, float x, float y);
|
||||||
|
|
||||||
|
[[nodiscard]] size_t size() const noexcept { return m_commands.size(); }
|
||||||
|
void clear() { m_commands.clear(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<Command>> m_commands;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace pdfengine {
|
||||||
|
|
||||||
|
// 2D Affine Transformation Matrix (3x3 matrix optimized for 2D)
|
||||||
|
// [ a b 0 ]
|
||||||
|
// [ c d 0 ]
|
||||||
|
// [ e f 1 ]
|
||||||
|
struct Matrix {
|
||||||
|
float a = 1.0f, b = 0.0f;
|
||||||
|
float c = 0.0f, d = 1.0f;
|
||||||
|
float e = 0.0f, f = 0.0f;
|
||||||
|
|
||||||
|
Matrix() = default;
|
||||||
|
Matrix(float a, float b, float c, float d, float e, float f)
|
||||||
|
: a(a), b(b), c(c), d(d), e(e), f(f) {}
|
||||||
|
|
||||||
|
// Multiply this matrix by another matrix
|
||||||
|
[[nodiscard]] Matrix multiply(const Matrix& other) const noexcept;
|
||||||
|
|
||||||
|
// Transform a 2D point using this matrix
|
||||||
|
void transform(float& x, float& y) const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
float r = 0.0f;
|
||||||
|
float g = 0.0f;
|
||||||
|
float b = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represents the current graphics state in a PDF document
|
||||||
|
struct GraphicsState {
|
||||||
|
Matrix ctm; // Current Transformation Matrix
|
||||||
|
Color fillColor;
|
||||||
|
Color strokeColor;
|
||||||
|
float lineWidth = 1.0f;
|
||||||
|
|
||||||
|
// In the future, this will also hold:
|
||||||
|
// - Clipping paths
|
||||||
|
// - Font state (current font, font size)
|
||||||
|
// - Dash patterns
|
||||||
|
// - Line cap/join styles
|
||||||
|
};
|
||||||
|
|
||||||
|
// Manages the q/Q stack of graphics states
|
||||||
|
class GraphicsStateStack {
|
||||||
|
public:
|
||||||
|
GraphicsStateStack();
|
||||||
|
|
||||||
|
// Corresponds to the 'q' operator (save graphics state)
|
||||||
|
void push();
|
||||||
|
|
||||||
|
// Corresponds to the 'Q' operator (restore graphics state)
|
||||||
|
void pop();
|
||||||
|
|
||||||
|
// Access the current active graphics state
|
||||||
|
[[nodiscard]] GraphicsState& current();
|
||||||
|
[[nodiscard]] const GraphicsState& current() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<GraphicsState> m_stack;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include <pdfengine/display_list.hpp>
|
||||||
|
|
||||||
|
namespace pdfengine {
|
||||||
|
|
||||||
|
void SaveStateCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
|
||||||
|
void RestoreStateCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
|
||||||
|
void SetTransformCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
|
||||||
|
void FillRectCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
|
||||||
|
void DrawTextCommand::accept(CommandVisitor& visitor) const { visitor.visit(*this); }
|
||||||
|
|
||||||
|
void DisplayList::addCommand(std::unique_ptr<Command> cmd) {
|
||||||
|
if (cmd) {
|
||||||
|
m_commands.push_back(std::move(cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayList::replay(CommandVisitor& visitor) const {
|
||||||
|
for (const auto& cmd : m_commands) {
|
||||||
|
cmd->accept(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayList::saveState() {
|
||||||
|
addCommand(std::make_unique<SaveStateCommand>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayList::restoreState() {
|
||||||
|
addCommand(std::make_unique<RestoreStateCommand>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayList::setTransform(const Matrix& m) {
|
||||||
|
addCommand(std::make_unique<SetTransformCommand>(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayList::fillRect(float x, float y, float w, float h) {
|
||||||
|
addCommand(std::make_unique<FillRectCommand>(x, y, w, h));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayList::drawText(const std::string& text, float x, float y) {
|
||||||
|
addCommand(std::make_unique<DrawTextCommand>(text, x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include <pdfengine/graphics_state.hpp>
|
||||||
|
|
||||||
|
namespace pdfengine {
|
||||||
|
|
||||||
|
Matrix Matrix::multiply(const Matrix& other) const noexcept {
|
||||||
|
return Matrix(
|
||||||
|
a * other.a + b * other.c,
|
||||||
|
a * other.b + b * other.d,
|
||||||
|
c * other.a + d * other.c,
|
||||||
|
c * other.b + d * other.d,
|
||||||
|
e * other.a + f * other.c + other.e,
|
||||||
|
e * other.b + f * other.d + other.f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Matrix::transform(float& x, float& y) const noexcept {
|
||||||
|
float newX = a * x + c * y + e;
|
||||||
|
float newY = b * x + d * y + f;
|
||||||
|
x = newX;
|
||||||
|
y = newY;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsStateStack::GraphicsStateStack() {
|
||||||
|
// A PDF always starts with one default graphics state on the stack.
|
||||||
|
m_stack.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsStateStack::push() {
|
||||||
|
// Duplicate the current state and push it onto the stack.
|
||||||
|
if (!m_stack.empty()) {
|
||||||
|
m_stack.push_back(m_stack.back());
|
||||||
|
} else {
|
||||||
|
m_stack.emplace_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsStateStack::pop() {
|
||||||
|
// The stack should never be empty, but we must protect against popping the initial state
|
||||||
|
// if the PDF is malformed (e.g. more 'Q' operators than 'q' operators).
|
||||||
|
if (m_stack.size() > 1) {
|
||||||
|
m_stack.pop_back();
|
||||||
|
} else {
|
||||||
|
// We could throw an exception or just ignore the invalid pop.
|
||||||
|
// For a resilient engine, ignoring is often better, but we could log a warning.
|
||||||
|
// For now, we do nothing to prevent crashing on the root state.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsState& GraphicsStateStack::current() {
|
||||||
|
return m_stack.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
const GraphicsState& GraphicsStateStack::current() const {
|
||||||
|
return m_stack.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
add_executable(pdfengine_smoke
|
add_executable(pdfengine_smoke
|
||||||
smoke_test.cpp
|
smoke_test.cpp
|
||||||
fonts_test.cpp
|
fonts_test.cpp
|
||||||
|
graphics_state_test.cpp
|
||||||
|
display_list_test.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(pdfengine_smoke
|
target_link_libraries(pdfengine_smoke
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <pdfengine/display_list.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace pdfengine;
|
||||||
|
|
||||||
|
// A simple visitor for testing that just records the sequence of visited commands.
|
||||||
|
class MockVisitor : public CommandVisitor {
|
||||||
|
public:
|
||||||
|
std::vector<std::string> calls;
|
||||||
|
|
||||||
|
void visit(const SaveStateCommand&) override { calls.push_back("SaveState"); }
|
||||||
|
void visit(const RestoreStateCommand&) override { calls.push_back("RestoreState"); }
|
||||||
|
void visit(const SetTransformCommand& cmd) override { calls.push_back("SetTransform(" + std::to_string(cmd.matrix.a) + ")"); }
|
||||||
|
void visit(const FillRectCommand& cmd) override { calls.push_back("FillRect(" + std::to_string(cmd.width) + ")"); }
|
||||||
|
void visit(const DrawTextCommand& cmd) override { calls.push_back("DrawText(" + cmd.text + ")"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(DisplayListTest, RecordAndReplay) {
|
||||||
|
DisplayList list;
|
||||||
|
|
||||||
|
EXPECT_EQ(list.size(), 0);
|
||||||
|
|
||||||
|
// Record some commands
|
||||||
|
list.saveState();
|
||||||
|
list.setTransform(Matrix(2.0f, 0.0f, 0.0f, 2.0f, 0.0f, 0.0f));
|
||||||
|
list.fillRect(10.0f, 10.0f, 100.0f, 50.0f);
|
||||||
|
list.drawText("Hello World", 20.0f, 30.0f);
|
||||||
|
list.restoreState();
|
||||||
|
|
||||||
|
EXPECT_EQ(list.size(), 5);
|
||||||
|
|
||||||
|
// Replay to the mock visitor
|
||||||
|
MockVisitor visitor;
|
||||||
|
list.replay(visitor);
|
||||||
|
|
||||||
|
ASSERT_EQ(visitor.calls.size(), 5);
|
||||||
|
EXPECT_EQ(visitor.calls[0], "SaveState");
|
||||||
|
EXPECT_EQ(visitor.calls[1], "SetTransform(2.000000)");
|
||||||
|
EXPECT_EQ(visitor.calls[2], "FillRect(100.000000)");
|
||||||
|
EXPECT_EQ(visitor.calls[3], "DrawText(Hello World)");
|
||||||
|
EXPECT_EQ(visitor.calls[4], "RestoreState");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DisplayListTest, Clear) {
|
||||||
|
DisplayList list;
|
||||||
|
list.saveState();
|
||||||
|
list.fillRect(0, 0, 10, 10);
|
||||||
|
EXPECT_EQ(list.size(), 2);
|
||||||
|
|
||||||
|
list.clear();
|
||||||
|
EXPECT_EQ(list.size(), 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <pdfengine/graphics_state.hpp>
|
||||||
|
|
||||||
|
using namespace pdfengine;
|
||||||
|
|
||||||
|
TEST(MatrixTest, DefaultIdentity) {
|
||||||
|
Matrix m;
|
||||||
|
EXPECT_FLOAT_EQ(m.a, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(m.b, 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(m.c, 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(m.d, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(m.e, 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(m.f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, Multiply) {
|
||||||
|
Matrix m1(2.0f, 0.0f, 0.0f, 2.0f, 10.0f, 20.0f);
|
||||||
|
Matrix m2(0.5f, 0.0f, 0.0f, 0.5f, -5.0f, -10.0f);
|
||||||
|
Matrix result = m1.multiply(m2);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(result.a, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.b, 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.c, 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.d, 1.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.e, 0.0f);
|
||||||
|
EXPECT_FLOAT_EQ(result.f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MatrixTest, TransformPoint) {
|
||||||
|
Matrix m(2.0f, 0.0f, 0.0f, 3.0f, 10.0f, 20.0f);
|
||||||
|
float x = 5.0f;
|
||||||
|
float y = 5.0f;
|
||||||
|
m.transform(x, y);
|
||||||
|
|
||||||
|
EXPECT_FLOAT_EQ(x, 20.0f); // 2*5 + 10
|
||||||
|
EXPECT_FLOAT_EQ(y, 35.0f); // 3*5 + 20
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GraphicsStateStackTest, PushPop) {
|
||||||
|
GraphicsStateStack stack;
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
stack.current().lineWidth = 5.0f;
|
||||||
|
|
||||||
|
// Push new state
|
||||||
|
stack.push();
|
||||||
|
EXPECT_FLOAT_EQ(stack.current().lineWidth, 5.0f);
|
||||||
|
|
||||||
|
// Modify current state
|
||||||
|
stack.current().lineWidth = 10.0f;
|
||||||
|
EXPECT_FLOAT_EQ(stack.current().lineWidth, 10.0f);
|
||||||
|
|
||||||
|
// Pop back to initial
|
||||||
|
stack.pop();
|
||||||
|
EXPECT_FLOAT_EQ(stack.current().lineWidth, 5.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GraphicsStateStackTest, PopEmptyProtection) {
|
||||||
|
GraphicsStateStack stack;
|
||||||
|
// Attempting to pop the root state should be safe (ignored)
|
||||||
|
stack.pop();
|
||||||
|
stack.current().lineWidth = 2.0f; // Should still be valid
|
||||||
|
EXPECT_FLOAT_EQ(stack.current().lineWidth, 2.0f);
|
||||||
|
}
|
||||||
Generated
+339
-64
@@ -8,8 +8,10 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6"
|
"react-dom": "^19.2.6",
|
||||||
|
"tailwindcss": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
@@ -57,7 +59,6 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -267,11 +268,31 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/core": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -476,7 +497,6 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -487,7 +507,6 @@
|
|||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@@ -498,7 +517,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -508,14 +526,12 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -526,7 +542,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -545,7 +560,6 @@
|
|||||||
"version": "0.130.0",
|
"version": "0.130.0",
|
||||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
|
||||||
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
|
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/Boshen"
|
"url": "https://github.com/sponsors/Boshen"
|
||||||
@@ -558,7 +572,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -575,7 +588,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -592,7 +604,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -609,7 +620,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -626,7 +636,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -643,7 +652,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -660,7 +668,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -677,7 +684,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -694,7 +700,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -711,7 +716,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -728,7 +732,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -745,7 +748,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -762,7 +764,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"wasm32"
|
"wasm32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -781,7 +782,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -798,7 +798,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -812,14 +811,269 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/node": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
|
"enhanced-resolve": "^5.21.0",
|
||||||
|
"jiti": "^2.6.1",
|
||||||
|
"lightningcss": "1.32.0",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"source-map-js": "^1.2.1",
|
||||||
|
"tailwindcss": "4.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tailwindcss/oxide-android-arm64": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-darwin-arm64": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-darwin-x64": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-freebsd-x64": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-linux-arm64-musl": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-linux-x64-gnu": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-linux-x64-musl": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.3.0",
|
||||||
|
"@tailwindcss/oxide-win32-x64-msvc": "4.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"@napi-rs/wasm-runtime",
|
||||||
|
"@emnapi/core",
|
||||||
|
"@emnapi/runtime",
|
||||||
|
"@tybys/wasm-util",
|
||||||
|
"@emnapi/wasi-threads",
|
||||||
|
"tslib"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "^1.10.0",
|
||||||
|
"@emnapi/runtime": "^1.10.0",
|
||||||
|
"@emnapi/wasi-threads": "^1.2.1",
|
||||||
|
"@napi-rs/wasm-runtime": "^1.1.4",
|
||||||
|
"@tybys/wasm-util": "^0.10.1",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/vite": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/node": "4.3.0",
|
||||||
|
"@tailwindcss/oxide": "4.3.0",
|
||||||
|
"tailwindcss": "4.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tybys/wasm-util": {
|
"node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.2",
|
"version": "0.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -851,9 +1105,8 @@
|
|||||||
"version": "24.12.4",
|
"version": "24.12.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
|
||||||
"integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
|
"integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
@@ -864,7 +1117,6 @@
|
|||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -924,7 +1176,6 @@
|
|||||||
"integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==",
|
"integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.59.3",
|
"@typescript-eslint/scope-manager": "8.59.3",
|
||||||
"@typescript-eslint/types": "8.59.3",
|
"@typescript-eslint/types": "8.59.3",
|
||||||
@@ -1155,7 +1406,6 @@
|
|||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -1246,7 +1496,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.10.12",
|
"baseline-browser-mapping": "^2.10.12",
|
||||||
"caniuse-lite": "^1.0.30001782",
|
"caniuse-lite": "^1.0.30001782",
|
||||||
@@ -1340,7 +1589,6 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -1353,6 +1601,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/enhanced-resolve": {
|
||||||
|
"version": "5.21.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
|
||||||
|
"integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.4",
|
||||||
|
"tapable": "^2.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
@@ -1382,7 +1643,6 @@
|
|||||||
"integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
|
"integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.2",
|
"@eslint-community/regexpp": "^4.12.2",
|
||||||
@@ -1584,7 +1844,6 @@
|
|||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -1653,7 +1912,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -1700,6 +1958,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/graceful-fs": {
|
||||||
|
"version": "4.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/hermes-estree": {
|
"node_modules/hermes-estree": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
||||||
@@ -1767,6 +2031,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/jiti": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@@ -1849,7 +2122,6 @@
|
|||||||
"version": "1.32.0",
|
"version": "1.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^2.0.3"
|
"detect-libc": "^2.0.3"
|
||||||
@@ -1882,7 +2154,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1903,7 +2174,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1924,7 +2194,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1945,7 +2214,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1966,7 +2234,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1987,7 +2254,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2008,7 +2274,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2029,7 +2294,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2050,7 +2314,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2071,7 +2334,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2092,7 +2354,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2132,6 +2393,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/magic-string": {
|
||||||
|
"version": "0.30.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "10.2.5",
|
"version": "10.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||||
@@ -2159,7 +2429,6 @@
|
|||||||
"version": "3.3.12",
|
"version": "3.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -2262,16 +2531,13 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -2283,7 +2549,6 @@
|
|||||||
"version": "8.5.14",
|
"version": "8.5.14",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -2333,7 +2598,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||||
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -2354,7 +2618,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
|
||||||
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
|
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oxc-project/types": "=0.130.0",
|
"@oxc-project/types": "=0.130.0",
|
||||||
@@ -2427,17 +2690,34 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwindcss": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/tapable": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.16",
|
"version": "0.2.16",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@@ -2467,7 +2747,6 @@
|
|||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true,
|
|
||||||
"license": "0BSD",
|
"license": "0BSD",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@@ -2490,7 +2769,6 @@
|
|||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -2527,7 +2805,7 @@
|
|||||||
"version": "7.16.0",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
@@ -2575,9 +2853,7 @@
|
|||||||
"version": "8.0.13",
|
"version": "8.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
|
||||||
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lightningcss": "^1.32.0",
|
"lightningcss": "^1.32.0",
|
||||||
"picomatch": "^4.0.4",
|
"picomatch": "^4.0.4",
|
||||||
@@ -2702,7 +2978,6 @@
|
|||||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6"
|
"react-dom": "^19.2.6",
|
||||||
|
"tailwindcss": "^4.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
|||||||
+42
-1
@@ -1,7 +1,8 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Toolbar } from './components/Toolbar';
|
import { Toolbar } from './components/Toolbar';
|
||||||
import { Sidebar } from './components/Sidebar';
|
import { Sidebar } from './components/Sidebar';
|
||||||
import { PDFViewer } from './viewer/PDFViewer';
|
import { PDFViewer } from './viewer/PDFViewer';
|
||||||
|
import type { PDFViewerRef } from './viewer/PDFViewer';
|
||||||
import type { Annotation } from './viewer/AnnotationLayer';
|
import type { Annotation } from './viewer/AnnotationLayer';
|
||||||
import { gatewayService } from './lib/gatewayService';
|
import { gatewayService } from './lib/gatewayService';
|
||||||
import type { DocumentInfo } from './lib/gatewayService';
|
import type { DocumentInfo } from './lib/gatewayService';
|
||||||
@@ -9,6 +10,7 @@ import { wasmLoader } from './lib/wasmLoader';
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const viewerRef = useRef<PDFViewerRef>(null);
|
||||||
const [documents, setDocuments] = useState<DocumentInfo[]>([]);
|
const [documents, setDocuments] = useState<DocumentInfo[]>([]);
|
||||||
const [selectedDocId, setSelectedDocId] = useState<string>('sample-doc-1');
|
const [selectedDocId, setSelectedDocId] = useState<string>('sample-doc-1');
|
||||||
const [activeDoc, setActiveDoc] = useState<DocumentInfo | null>(null);
|
const [activeDoc, setActiveDoc] = useState<DocumentInfo | null>(null);
|
||||||
@@ -25,6 +27,35 @@ function App() {
|
|||||||
const [sidebarTab, setSidebarTab] = useState<'documents' | 'annotations' | 'outline'>('documents');
|
const [sidebarTab, setSidebarTab] = useState<'documents' | 'annotations' | 'outline'>('documents');
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
// Search State
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [searchResultCount, setSearchResultCount] = useState(0);
|
||||||
|
const [searchCurrentMatch, setSearchCurrentMatch] = useState(0);
|
||||||
|
|
||||||
|
const handleSearch = (query: string) => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
// Mock search results for Phase 0
|
||||||
|
if (query) {
|
||||||
|
setSearchResultCount(5);
|
||||||
|
setSearchCurrentMatch(0);
|
||||||
|
} else {
|
||||||
|
setSearchResultCount(0);
|
||||||
|
setSearchCurrentMatch(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchNext = () => {
|
||||||
|
if (searchResultCount > 0) {
|
||||||
|
setSearchCurrentMatch((prev) => (prev + 1) % searchResultCount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchPrev = () => {
|
||||||
|
if (searchResultCount > 0) {
|
||||||
|
setSearchCurrentMatch((prev) => (prev - 1 + searchResultCount) % searchResultCount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Check Gateway Health on mount
|
// Check Gateway Health on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkHealth = async () => {
|
const checkHealth = async () => {
|
||||||
@@ -131,6 +162,11 @@ function App() {
|
|||||||
totalPages={activeDoc?.totalPages || 1}
|
totalPages={activeDoc?.totalPages || 1}
|
||||||
onUploadStart={handleUploadStart}
|
onUploadStart={handleUploadStart}
|
||||||
backendHealthy={backendHealthy}
|
backendHealthy={backendHealthy}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
searchResultCount={searchResultCount}
|
||||||
|
searchCurrentMatch={searchCurrentMatch}
|
||||||
|
onSearchNext={handleSearchNext}
|
||||||
|
onSearchPrev={handleSearchPrev}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-1 w-full flex overflow-hidden">
|
<div className="flex-1 w-full flex overflow-hidden">
|
||||||
@@ -143,6 +179,9 @@ function App() {
|
|||||||
totalPages={activeDoc?.totalPages || 0}
|
totalPages={activeDoc?.totalPages || 0}
|
||||||
activeTab={sidebarTab}
|
activeTab={sidebarTab}
|
||||||
setActiveTab={setSidebarTab}
|
setActiveTab={setSidebarTab}
|
||||||
|
onNavigateToPage={(pageIndex) => {
|
||||||
|
viewerRef.current?.scrollToPage(pageIndex);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main PDF Scroll Viewer Area */}
|
{/* Main PDF Scroll Viewer Area */}
|
||||||
@@ -156,12 +195,14 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
) : activeDoc ? (
|
) : activeDoc ? (
|
||||||
<PDFViewer
|
<PDFViewer
|
||||||
|
ref={viewerRef}
|
||||||
documentId={activeDoc.id}
|
documentId={activeDoc.id}
|
||||||
totalPages={activeDoc.totalPages}
|
totalPages={activeDoc.totalPages}
|
||||||
zoom={zoom}
|
zoom={zoom}
|
||||||
rotation={rotation}
|
rotation={rotation}
|
||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
annotations={annotations}
|
annotations={annotations}
|
||||||
|
searchQuery={searchQuery}
|
||||||
onAnnotationAdded={handleAnnotationAdded}
|
onAnnotationAdded={handleAnnotationAdded}
|
||||||
onPageVisible={setCurrentPage}
|
onPageVisible={setCurrentPage}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface SearchBarProps {
|
||||||
|
onSearch: (query: string) => void;
|
||||||
|
resultCount: number;
|
||||||
|
currentMatch: number;
|
||||||
|
onNext: () => void;
|
||||||
|
onPrev: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchBar: React.FC<SearchBarProps> = ({
|
||||||
|
onSearch,
|
||||||
|
resultCount,
|
||||||
|
currentMatch,
|
||||||
|
onNext,
|
||||||
|
onPrev,
|
||||||
|
}) => {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
onSearch(query);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center bg-slate-900/40 border border-slate-700/70 rounded-xl px-3 py-1.5 gap-2 shadow-inner">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" className="text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
className="bg-transparent border-none outline-none text-slate-100 text-sm w-32 placeholder-slate-500"
|
||||||
|
/>
|
||||||
|
{query && (
|
||||||
|
<div className="flex items-center gap-1 text-xs text-slate-400 font-semibold border-l border-slate-700 pl-2">
|
||||||
|
<span>{resultCount > 0 ? `${currentMatch + 1}/${resultCount}` : '0/0'}</span>
|
||||||
|
<div className="flex flex-col gap-0.5 ml-1">
|
||||||
|
<button onClick={onPrev} className="hover:text-white" title="Previous match">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 15l7-7 7 7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button onClick={onNext} className="hover:text-white" title="Next match">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { DocumentInfo } from '../lib/gatewayService';
|
import type { DocumentInfo } from '../lib/gatewayService';
|
||||||
import type { Annotation } from '../viewer/AnnotationLayer';
|
import type { Annotation } from '../viewer/AnnotationLayer';
|
||||||
|
import { Thumbnail } from './Thumbnail';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
documents: DocumentInfo[];
|
documents: DocumentInfo[];
|
||||||
@@ -10,6 +11,7 @@ interface SidebarProps {
|
|||||||
totalPages: number;
|
totalPages: number;
|
||||||
activeTab: 'documents' | 'annotations' | 'outline';
|
activeTab: 'documents' | 'annotations' | 'outline';
|
||||||
setActiveTab: (tab: 'documents' | 'annotations' | 'outline') => void;
|
setActiveTab: (tab: 'documents' | 'annotations' | 'outline') => void;
|
||||||
|
onNavigateToPage?: (pageIndex: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Sidebar: React.FC<SidebarProps> = ({
|
export const Sidebar: React.FC<SidebarProps> = ({
|
||||||
@@ -20,6 +22,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
totalPages,
|
totalPages,
|
||||||
activeTab,
|
activeTab,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
|
onNavigateToPage,
|
||||||
}) => {
|
}) => {
|
||||||
const formatBytes = (bytes: number) => {
|
const formatBytes = (bytes: number) => {
|
||||||
if (bytes === 0) return '0 Bytes';
|
if (bytes === 0) return '0 Bytes';
|
||||||
@@ -104,14 +107,12 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
{activeTab === 'outline' && (
|
{activeTab === 'outline' && (
|
||||||
<div className="thumbnails-grid">
|
<div className="thumbnails-grid">
|
||||||
{Array.from({ length: totalPages }).map((_, idx) => (
|
{Array.from({ length: totalPages }).map((_, idx) => (
|
||||||
<div key={idx} className="thumbnail-card">
|
<Thumbnail
|
||||||
<div className="thumbnail-preview">
|
key={idx}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" className="thumbnail-svg-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
documentId={selectedDocumentId}
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
pageIndex={idx}
|
||||||
</svg>
|
onClick={() => onNavigateToPage?.(idx)}
|
||||||
<span className="thumbnail-label">Page {idx + 1}</span>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { gatewayService } from '../lib/gatewayService';
|
||||||
|
|
||||||
|
interface ThumbnailProps {
|
||||||
|
documentId: string;
|
||||||
|
pageIndex: number;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Thumbnail: React.FC<ThumbnailProps> = ({
|
||||||
|
documentId,
|
||||||
|
pageIndex,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
const fetchThumbnail = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
// Request a lower resolution/zoom image for the thumbnail
|
||||||
|
const url = await gatewayService.renderPage({
|
||||||
|
documentId,
|
||||||
|
pageIndex,
|
||||||
|
zoom: 0.2, // Small zoom for thumbnail size
|
||||||
|
rotation: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
setImageUrl(url);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to load thumbnail for page ${pageIndex}`, err);
|
||||||
|
} finally {
|
||||||
|
if (active) setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchThumbnail();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, [documentId, pageIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="thumbnail-card" onClick={onClick}>
|
||||||
|
<div className="thumbnail-preview overflow-hidden bg-white hover:border-indigo-500 transition-colors">
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full w-full bg-slate-900">
|
||||||
|
<div className="w-5 h-5 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin mb-2" />
|
||||||
|
<span className="thumbnail-label">Loading...</span>
|
||||||
|
</div>
|
||||||
|
) : imageUrl ? (
|
||||||
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
alt={`Page ${pageIndex + 1}`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center justify-center h-full w-full">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" className="thumbnail-svg-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
<span className="thumbnail-label text-rose-400">Error</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="thumbnail-label text-center mt-1">Page {pageIndex + 1}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { SearchBar } from './SearchBar';
|
||||||
|
|
||||||
interface ToolbarProps {
|
interface ToolbarProps {
|
||||||
zoom: number;
|
zoom: number;
|
||||||
@@ -13,6 +14,11 @@ interface ToolbarProps {
|
|||||||
backendHealthy: boolean | null;
|
backendHealthy: boolean | null;
|
||||||
wasmEngineInfo?: string;
|
wasmEngineInfo?: string;
|
||||||
wasmHasSkia?: boolean;
|
wasmHasSkia?: boolean;
|
||||||
|
onSearch: (query: string) => void;
|
||||||
|
searchResultCount: number;
|
||||||
|
searchCurrentMatch: number;
|
||||||
|
onSearchNext: () => void;
|
||||||
|
onSearchPrev: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Toolbar: React.FC<ToolbarProps> = ({
|
export const Toolbar: React.FC<ToolbarProps> = ({
|
||||||
@@ -26,8 +32,11 @@ export const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
totalPages,
|
totalPages,
|
||||||
onUploadStart,
|
onUploadStart,
|
||||||
backendHealthy,
|
backendHealthy,
|
||||||
wasmEngineInfo,
|
onSearch,
|
||||||
wasmHasSkia,
|
searchResultCount,
|
||||||
|
searchCurrentMatch,
|
||||||
|
onSearchNext,
|
||||||
|
onSearchPrev,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const handleZoomPercentSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleZoomPercentSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
@@ -125,6 +134,14 @@ export const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
{/* Right Tools (Active tool highlights, Upload) */}
|
{/* Right Tools (Active tool highlights, Upload) */}
|
||||||
<div className="toolbar-section gap-3">
|
<div className="toolbar-section gap-3">
|
||||||
<div className="tool-selector">
|
<div className="tool-selector">
|
||||||
|
<button
|
||||||
|
onClick={() => onActiveToolChange('pan')}
|
||||||
|
className={`tool-btn ${activeTool === 'pan' ? 'active' : ''}`}
|
||||||
|
title="Pan (Drag)"
|
||||||
|
>
|
||||||
|
Pan
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => onActiveToolChange('select')}
|
onClick={() => onActiveToolChange('select')}
|
||||||
className={`tool-btn ${activeTool === 'select' ? 'active' : ''}`}
|
className={`tool-btn ${activeTool === 'select' ? 'active' : ''}`}
|
||||||
@@ -150,6 +167,15 @@ export const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* SEARCH BAR */}
|
||||||
|
<SearchBar
|
||||||
|
onSearch={onSearch}
|
||||||
|
resultCount={searchResultCount}
|
||||||
|
currentMatch={searchCurrentMatch}
|
||||||
|
onNext={onSearchNext}
|
||||||
|
onPrev={onSearchPrev}
|
||||||
|
/>
|
||||||
|
|
||||||
<label className="upload-btn">
|
<label className="upload-btn">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--sans: 'Outfit', system-ui, -apple-system, sans-serif;
|
--sans: 'Outfit', system-ui, -apple-system, sans-serif;
|
||||||
|
|||||||
@@ -42,13 +42,17 @@ class GatewayService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async listDocuments(): Promise<DocumentInfo[]> {
|
async listDocuments(): Promise<DocumentInfo[]> {
|
||||||
const response = await fetch(`${this.baseUrl}/documents`);
|
try {
|
||||||
if (response.status === 501) {
|
const response = await fetch(`${this.baseUrl}/documents`);
|
||||||
// Return mocked document list for Phase 0 scaffolding
|
if (response.status === 501) {
|
||||||
|
return this.getMockDocuments();
|
||||||
|
}
|
||||||
|
if (!response.ok) throw new Error(`Failed to list documents: ${response.statusText}`);
|
||||||
|
return response.json();
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback if backend is not running at all
|
||||||
return this.getMockDocuments();
|
return this.getMockDocuments();
|
||||||
}
|
}
|
||||||
if (!response.ok) throw new Error(`Failed to list documents: ${response.statusText}`);
|
|
||||||
return response.json();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadDocument(file: File): Promise<DocumentInfo> {
|
async uploadDocument(file: File): Promise<DocumentInfo> {
|
||||||
@@ -81,14 +85,20 @@ class GatewayService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getDocument(id: string): Promise<DocumentInfo> {
|
async getDocument(id: string): Promise<DocumentInfo> {
|
||||||
const response = await fetch(`${this.baseUrl}/documents/${id}`);
|
try {
|
||||||
if (response.status === 501) {
|
const response = await fetch(`${this.baseUrl}/documents/${id}`);
|
||||||
|
if (response.status === 501) {
|
||||||
|
const mock = this.getMockDocuments().find(d => d.id === id);
|
||||||
|
if (!mock) throw new Error('Document not found');
|
||||||
|
return mock;
|
||||||
|
}
|
||||||
|
if (!response.ok) throw new Error(`Failed to fetch document metadata: ${response.statusText}`);
|
||||||
|
return response.json();
|
||||||
|
} catch (err) {
|
||||||
const mock = this.getMockDocuments().find(d => d.id === id);
|
const mock = this.getMockDocuments().find(d => d.id === id);
|
||||||
if (!mock) throw new Error('Document not found');
|
if (!mock) throw new Error('Document not found');
|
||||||
return mock;
|
return mock;
|
||||||
}
|
}
|
||||||
if (!response.ok) throw new Error(`Failed to fetch document metadata: ${response.statusText}`);
|
|
||||||
return response.json();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteDocument(id: string): Promise<{ success: boolean }> {
|
async deleteDocument(id: string): Promise<{ success: boolean }> {
|
||||||
@@ -103,24 +113,26 @@ class GatewayService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async renderPage(params: RenderParams): Promise<string> {
|
async renderPage(params: RenderParams): Promise<string> {
|
||||||
// Returns object URL or base64 data for the rendered page
|
try {
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
page: params.pageIndex.toString(),
|
page: params.pageIndex.toString(),
|
||||||
zoom: params.zoom.toString(),
|
zoom: params.zoom.toString(),
|
||||||
rotation: params.rotation.toString(),
|
rotation: params.rotation.toString(),
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
const url = `${this.baseUrl}/render/${params.documentId}?${query}`;
|
const url = `${this.baseUrl}/render/${params.documentId}?${query}`;
|
||||||
|
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (response.status === 501) {
|
if (response.status === 501) {
|
||||||
// Fallback: Generate a high-fidelity mock SVG/Canvas data URL for the page
|
return this.generateMockPage(params.pageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`Page render failed: ${response.statusText}`);
|
||||||
|
const blob = await response.blob();
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
} catch (err) {
|
||||||
return this.generateMockPage(params.pageIndex);
|
return this.generateMockPage(params.pageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`Page render failed: ${response.statusText}`);
|
|
||||||
const blob = await response.blob();
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyEdits(documentId: string, operations: EditOperation[]): Promise<{ success: boolean; newDocumentId: string }> {
|
async applyEdits(documentId: string, operations: EditOperation[]): Promise<{ success: boolean; newDocumentId: string }> {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { SelectionLayer } from './SelectionLayer';
|
|||||||
import { AnnotationLayer } from './AnnotationLayer';
|
import { AnnotationLayer } from './AnnotationLayer';
|
||||||
import type { Annotation } from './AnnotationLayer';
|
import type { Annotation } from './AnnotationLayer';
|
||||||
import { OverlayLayer } from './OverlayLayer';
|
import { OverlayLayer } from './OverlayLayer';
|
||||||
|
import { SearchOverlayLayer } from './SearchOverlayLayer';
|
||||||
import type { Rect } from '../lib/coordinateMapping';
|
import type { Rect } from '../lib/coordinateMapping';
|
||||||
import { gatewayService } from '../lib/gatewayService';
|
import { gatewayService } from '../lib/gatewayService';
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ interface PDFViewerProps {
|
|||||||
rotation: number;
|
rotation: number;
|
||||||
activeTool: string;
|
activeTool: string;
|
||||||
annotations: Annotation[];
|
annotations: Annotation[];
|
||||||
|
searchQuery?: string;
|
||||||
onAnnotationAdded?: (anno: Annotation) => void;
|
onAnnotationAdded?: (anno: Annotation) => void;
|
||||||
onPageVisible?: (pageIndex: number) => void;
|
onPageVisible?: (pageIndex: number) => void;
|
||||||
}
|
}
|
||||||
@@ -27,16 +29,21 @@ interface PageLayout {
|
|||||||
|
|
||||||
const generateUniqueId = () => `anno_${Math.random().toString(36).substring(2, 11)}`;
|
const generateUniqueId = () => `anno_${Math.random().toString(36).substring(2, 11)}`;
|
||||||
|
|
||||||
export const PDFViewer: React.FC<PDFViewerProps> = ({
|
export interface PDFViewerRef {
|
||||||
|
scrollToPage: (pageIndex: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PDFViewer = React.forwardRef<PDFViewerRef, PDFViewerProps>(({
|
||||||
documentId,
|
documentId,
|
||||||
totalPages,
|
totalPages,
|
||||||
zoom,
|
zoom,
|
||||||
rotation,
|
rotation,
|
||||||
activeTool,
|
activeTool,
|
||||||
annotations,
|
annotations,
|
||||||
|
searchQuery,
|
||||||
onAnnotationAdded,
|
onAnnotationAdded,
|
||||||
onPageVisible,
|
onPageVisible,
|
||||||
}) => {
|
}, ref) => {
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [scrollPosition, setScrollPosition] = useState({ scrollLeft: 0, scrollTop: 0 });
|
const [scrollPosition, setScrollPosition] = useState({ scrollLeft: 0, scrollTop: 0 });
|
||||||
const [renderedPages, setRenderedPages] = useState<string[]>([]);
|
const [renderedPages, setRenderedPages] = useState<string[]>([]);
|
||||||
@@ -85,6 +92,14 @@ export const PDFViewer: React.FC<PDFViewerProps> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => ({
|
||||||
|
scrollToPage: (pageIndex: number) => {
|
||||||
|
if (containerRef.current && pageLayouts[pageIndex]) {
|
||||||
|
containerRef.current.scrollTop = pageLayouts[pageIndex].top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateSize = () => {
|
const updateSize = () => {
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
@@ -190,11 +205,45 @@ export const PDFViewer: React.FC<PDFViewerProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [isPanning, setIsPanning] = useState(false);
|
||||||
|
const [panStart, setPanStart] = useState({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 });
|
||||||
|
|
||||||
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
|
if (activeTool === 'pan' && containerRef.current) {
|
||||||
|
setIsPanning(true);
|
||||||
|
setPanStart({
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY,
|
||||||
|
scrollLeft: containerRef.current.scrollLeft,
|
||||||
|
scrollTop: containerRef.current.scrollTop,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (e: React.MouseEvent) => {
|
||||||
|
if (isPanning && containerRef.current) {
|
||||||
|
const dx = e.clientX - panStart.x;
|
||||||
|
const dy = e.clientY - panStart.y;
|
||||||
|
containerRef.current.scrollLeft = panStart.scrollLeft - dx;
|
||||||
|
containerRef.current.scrollTop = panStart.scrollTop - dy;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
if (isPanning) {
|
||||||
|
setIsPanning(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
className="viewer-viewport"
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
onMouseUp={handleMouseUp}
|
||||||
|
onMouseLeave={handleMouseUp}
|
||||||
|
className={`viewer-viewport ${activeTool === 'pan' ? (isPanning ? 'cursor-grabbing' : 'cursor-grab') : ''}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="viewer-content-container"
|
className="viewer-content-container"
|
||||||
@@ -250,6 +299,15 @@ export const PDFViewer: React.FC<PDFViewerProps> = ({
|
|||||||
height={page.height}
|
height={page.height}
|
||||||
activeTool={activeTool}
|
activeTool={activeTool}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Search highlights overlay */}
|
||||||
|
<SearchOverlayLayer
|
||||||
|
pageIndex={page.index}
|
||||||
|
width={page.width}
|
||||||
|
height={page.height}
|
||||||
|
zoom={zoom}
|
||||||
|
searchQuery={searchQuery || ''}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="page-loading-state">
|
<div className="page-loading-state">
|
||||||
@@ -263,4 +321,4 @@ export const PDFViewer: React.FC<PDFViewerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SearchOverlayLayerProps {
|
||||||
|
pageIndex: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
zoom: number;
|
||||||
|
searchQuery: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchOverlayLayer: React.FC<SearchOverlayLayerProps> = ({
|
||||||
|
pageIndex,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
zoom,
|
||||||
|
searchQuery,
|
||||||
|
}) => {
|
||||||
|
if (!searchQuery) return null;
|
||||||
|
|
||||||
|
// Mock some search results based on the query for Phase 0/1
|
||||||
|
// We'll just generate deterministic-looking boxes so it looks like it found something
|
||||||
|
const mockResults = [];
|
||||||
|
const hash = searchQuery.length + pageIndex;
|
||||||
|
|
||||||
|
if (hash % 3 !== 0) {
|
||||||
|
mockResults.push({
|
||||||
|
x: 100 * zoom,
|
||||||
|
y: (150 + hash * 10) * zoom,
|
||||||
|
width: 120 * zoom,
|
||||||
|
height: 18 * zoom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hash % 2 === 0) {
|
||||||
|
mockResults.push({
|
||||||
|
x: 300 * zoom,
|
||||||
|
y: (250 + hash * 5) * zoom,
|
||||||
|
width: 80 * zoom,
|
||||||
|
height: 18 * zoom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute top-0 left-0 pointer-events-none z-20"
|
||||||
|
style={{ width: `${width}px`, height: `${height}px` }}
|
||||||
|
>
|
||||||
|
{mockResults.map((rect, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="absolute bg-yellow-400/40 border border-yellow-500/60 rounded-sm"
|
||||||
|
style={{
|
||||||
|
left: `${rect.x}px`,
|
||||||
|
top: `${rect.y}px`,
|
||||||
|
width: `${rect.width}px`,
|
||||||
|
height: `${rect.height}px`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react(), tailwindcss()],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user