First commit

This commit is contained in:
2025-12-25 11:16:59 +01:00
commit 0c5ca09a63
720 changed files with 329234 additions and 0 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
loglevel=error

249
Animations.md Normal file
View File

@@ -0,0 +1,249 @@
# Animations in Unify
Unify's animation system provides a **declarative and flexible way** to apply smooth transitions, visual effects, and interactive animations to your UI components. Whether it's for simple property changes, complex transitions, or interactive user feedback, Unify makes it easy to animate your components in a clear and structured way.
---
## 1. Introduction to Animations
Animations in Unify are **object-driven**, meaning animations are attached directly to **Unify objects** such as panels, buttons, and other UI components. The animation system uses **keyframes** to define transitions over time, allowing properties like `opacity`, `transform`, `background`, etc., to change smoothly.
Animations are executed based on **events** (e.g., `click`, `load`, `hover`) or can be triggered explicitly using the `play()` method.
---
## 2. Basic Animation Example
To define an animation, you typically use the **`createAnimation`** method, which creates a new animation instance. You can then define **keyframes** and set the properties for the element at various points during the animation.
### Example: Color Change Animation
Heres an example where we animate the background color of an element from **blue** to **green**:
```js
class ColorChangeAnimation {
element = document.createElement("div");
width = 100;
height = 100;
margin = 20;
create() {
// Create the animation
this.animation = this.createAnimation("colorChange");
// Define keyframes
var key = this.animation.createKeyFrame(0);
key.setProperty("background", "#03a9f4"); // Initial color
key = this.animation.createKeyFrame(100);
key.setProperty("background", "#a6e22e"); // Final color
}
async click() {
// Play the animation over 2 seconds when clicked
await this.animation.play("2s");
}
}
```
### Key Concepts:
* **Create the animation**: The `createAnimation("colorChange")` method creates a new animation instance.
* **Define keyframes**: Keyframes define the start and end states of the animation (e.g., `background` changing from blue to green).
* **Trigger the animation**: The `play()` method starts the animation and allows you to define its duration (e.g., `2s` for 2 seconds).
---
## 3. Animating Multiple Properties
You can animate multiple properties simultaneously by setting **multiple keyframes** for different CSS properties. This allows you to create more complex animations that change several aspects of the UI at once.
### Example: Fading and Scaling
Heres an example of an animation that fades an element in while simultaneously scaling it up:
```js
class FadeAndScale {
element = document.createElement("div");
width = 100;
height = 100;
margin = 20;
create() {
// Create the animation
this.animation = this.createAnimation("fadeAndScale");
// Fade in
var key = this.animation.createKeyFrame(0);
key.setProperty("opacity", "0");
key.setProperty("transform", "scale(0.5)");
// Fade and scale up
key = this.animation.createKeyFrame(100);
key.setProperty("opacity", "1");
key.setProperty("transform", "scale(1)");
}
async click() {
// Play the fade and scale animation
await this.animation.play("2s");
}
}
```
In this example:
* **Opacity** and **transform** properties are animated together.
* The element fades in from `opacity: 0` to `opacity: 1` while scaling from `scale(0.5)` to `scale(1)`.
---
## 4. Triggering Animations Based on Events
You can trigger animations in response to various user events like `click`, `hover`, or `focus`. This makes the animations interactive, responding to user actions.
### Example: Button Click Animation
Lets animate a button's background color when its clicked:
```js
import button from '/elements/button.js';
export default class AnimatedButton extends button {
label = "Click Me";
async click() {
// Create a new animation
this.animation = this.createAnimation("buttonClick");
var key = this.animation.createKeyFrame(0);
key.setProperty("background-color", "#03a9f4"); // Initial color
key = this.animation.createKeyFrame(100);
key.setProperty("background-color", "#f92672"); // Final color
// Play the animation for 2 seconds
await this.animation.play("2s");
}
}
```
When the user clicks the button, the `click()` method triggers the animation that changes the background color from **blue** (`#03a9f4`) to **red** (`#f92672`).
---
## 5. Sequential and Chained Animations
You can also chain animations together to create more complex effects. For instance, you can have one animation complete before the next one starts.
### Example: Sequential Animations
```js
class SequentialAnimations {
element = document.createElement("div");
width = 100;
height = 100;
margin = 20;
create() {
// Animation 1: Fade in
this.animation1 = this.createAnimation("fadeIn");
var key = this.animation1.createKeyFrame(0);
key.setProperty("opacity", "0");
key = this.animation1.createKeyFrame(100);
key.setProperty("opacity", "1");
// Animation 2: Scale up
this.animation2 = this.createAnimation("scaleUp");
key = this.animation2.createKeyFrame(0);
key.setProperty("transform", "scale(0.5)");
key = this.animation2.createKeyFrame(100);
key.setProperty("transform", "scale(1)");
}
async click() {
// Play fade-in animation first
await this.animation1.play("2s");
// After fade-in, play scale-up animation
await this.animation2.play("2s");
alert("Both animations are complete!");
}
}
```
### Explanation:
* **Sequential Execution**: The `await` ensures that the animations play one after another. First, the **fade-in** animation plays, followed by the **scale-up** animation.
* **Multiple Animations**: The keyframes for each animation are created independently, and their respective properties change over time.
---
## 6. Animating Transitions Between States
Animations are often used for transitioning between different **states** of an application, such as showing or hiding a panel, or transitioning between pages.
### Example: Sliding In a Panel
```js
import panel from '/elements/panel.js';
export default class SlidePanel extends panel {
isOpen = false;
async click() {
if (!this.isOpen) {
// Slide in animation
this.animation = this.createAnimation("slideIn");
var key = this.animation.createKeyFrame(0);
key.setProperty("transform", "translateX(100%)");
key = this.animation.createKeyFrame(100);
key.setProperty("transform", "translateX(0)");
await this.animation.play("0.5s");
this.isOpen = true;
} else {
// Slide out animation
this.animation = this.createAnimation("slideOut");
var key = this.animation.createKeyFrame(0);
key.setProperty("transform", "translateX(0)");
key = this.animation.createKeyFrame(100);
key.setProperty("transform", "translateX(100%)");
await this.animation.play("0.5s");
this.isOpen = false;
}
}
}
```
### Steps:
1. **Slide In**: The panel slides in from the right (by transitioning `translateX(100%)` to `translateX(0)`).
2. **Slide Out**: When clicked again, the panel slides out of view (by transitioning `translateX(0)` to `translateX(100%)`).
3. **Interactive**: The `isOpen` flag determines whether to slide in or slide out, toggling the state.
---
## 7. Conclusion
Unifys animation system provides a **flexible, declarative approach** to animating UI elements. Whether you're animating simple property changes or complex transitions between multiple states, you can easily add animations to your UI.
Key features:
* **Multiple keyframes** to animate multiple properties simultaneously.
* **Event-driven animations** triggered by user interactions (e.g., clicks, page loads).
* **Sequential and parallel animations** to create complex visual effects.
* **Smooth transitions** for UI states like opening/closing panels or transitioning between pages.
By leveraging Unifys animation system, you can craft engaging and interactive user experiences with minimal effort.

215
Permissions.md Normal file
View File

@@ -0,0 +1,215 @@
# Permissions: Groups, Scopes, Authorization Rules
In Unify, the **permissions system** is designed to give you full control over who can **read**, **write**, or **delete** data, while allowing you to enforce security at various levels of the application.
Unify offers a **flexible**, **declarative** approach to managing permissions across different objects, allowing you to define **authorization rules** based on:
* **User Groups** (e.g., admin, guest, member)
* **Scopes** (e.g., a column, a table, or an entire resource)
* **Actions** (e.g., READ, WRITE, DELETE)
## 1. Groups: Defining User Roles
In Unify, **Groups** represent user roles, and each group has certain **permissions** associated with it. These groups can be used to control access at various levels, such as:
* Viewing data
* Editing data
* Deleting data
### Example: Defining Groups
```js
import groups from '/user/group/user.group.permission.js';
export default class user extends table {
username = new username();
email = new email();
groups = new groups(); // Associate user with groups
}
```
### Common Use Case
For instance, you might have:
* **Visitor**: Can only **read** public information
* **Member**: Can **read** and **write** content
* **Admin**: Can perform all actions, including **delete**
You can easily enforce these permissions using:
```js
permission() {
this.allow(groups.visitor, "READ"); // Visitors can read
this.allow(groups.member, "READ"); // Members can read
this.allow(groups.member, "WRITE"); // Members can write
this.allow(groups.admin, "READ"); // Admins can read
this.allow(groups.admin, "WRITE"); // Admins can write
this.allow(groups.admin, "DELETE"); // Admins can delete
}
```
---
## 2. Scopes: Fine-Grained Permission Control
**Scopes** allow you to define permissions not just for entire tables or models, but for **specific fields** or **columns** within those models.
For example, a user might be allowed to **read** all the data but only **edit** a certain field, such as a profile description or a specific product attribute.
### Example: Scope-Based Permissions
```js
permission() {
// Allow "member" group to read all users but only update email
this.allow(groups.member, "READ");
this.allow(groups.member, "WRITE", "email"); // Allow update on email only
this.allow(groups.admin, "READ");
this.allow(groups.admin, "WRITE");
this.allow(groups.admin, "DELETE");
}
```
In this example:
* **Members** can **read** all data but only **update** the `email` field.
* **Admins** have full access (can **read**, **write**, and **delete** all fields).
You can also control **views** or **actions** like **deleting** based on a particular column or resource.
---
## 3. Authorization Rules: Controlling Access
**Authorization rules** are how you declare which groups have what types of access to your tables, fields, or methods. These rules govern **what actions** a group can perform on **what objects**.
You can define rules for specific actions such as:
* **READ**: View data
* **WRITE**: Modify data
* **DELETE**: Remove data
Each object can implement an associated **`permission()`** method to specify which group can perform what action on the object.
### Example: Implementing Authorization Rules
```js
export default class news extends table {
title = new title();
body = new body();
permission() {
this.allow(groups.visitor, "READ"); // Visitors can read the news
this.allow(groups.member, "WRITE"); // Members can write/edit news
this.allow(groups.admin, "READ"); // Admins can read
this.allow(groups.admin, "WRITE"); // Admins can write
this.allow(groups.admin, "DELETE"); // Admins can delete
}
}
```
---
## 4. Implementing Permissions in `node` Methods
In addition to controlling UI elements like columns, you may want to enforce permissions when executing actions on the server (such as creating, updating, or deleting records). This is where **manual permission checks** come into play.
### Example: Using Permissions in `node` Methods
```js
node async deleteRow(client) {
const user = client.user;
// Check if the user has permission to delete
if (!user.hasPermission("DELETE_NEWS")) {
throw new Error("Permission denied: User cannot delete");
}
this.delete(); // Perform the delete action
}
```
In this example, the `deleteRow` method checks whether the **current user** has permission to delete the record. If they do not, it throws an error and prevents the action.
---
## 5. Permissions with Collections
Since collections may contain multiple rows (or objects), you may also need to control access at the **collection level**. This allows you to define permissions that apply across the entire dataset.
### Example: Permissions for Collection
```js
export default class newsListTableBody extends renderCollection, gridViewBody {
permission() {
this.allow(groups.visitor, "READ"); // Visitors can read all news in the collection
this.allow(groups.member, "WRITE"); // Members can write new news items
this.allow(groups.admin, "DELETE"); // Admins can delete news items
}
}
```
This ensures that each row in the collection respects the users permissions when they try to **view**, **create**, **update**, or **delete** entries.
---
## 6. Combining Permissions with UI
In Unify, you **don't** have to manually manage every UI element (such as showing or hiding a button) based on permissions. You can use the `permission()` method to dynamically control UI elements' visibility and accessibility by the roles assigned.
For instance:
```js
node async click() {
if (this.user.hasPermission("EDIT_NEWS")) {
this.showEditForm();
} else {
throw new Error("Permission denied: Cannot edit news");
}
}
```
You can pair the **permissions check** with UI changes to ensure users can only interact with elements they are authorized to use.
---
## 7. Custom Authorization Logic
You can also write custom authorization logic, such as role hierarchies (where one group has broader permissions than another).
### Example: Custom Authorization
```js
node async deleteRow(client) {
const user = client.user;
// Only admin or user who created the news can delete it
if (!(user.hasPermission("DELETE_NEWS") || this.createdBy === user.id)) {
throw new Error("Permission denied: User cannot delete this news item");
}
this.delete(); // Proceed to delete if authorized
}
```
This custom rule checks whether the **current user** is an **admin** or the **creator** of the news item before allowing them to delete it.
---
## Summary of Permissions System
| Element | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **Groups** | Roles that define the access level of a user (e.g., Admin, Member, Visitor) |
| **Scopes** | Granular permissions tied to specific fields, columns, or actions within a model |
| **Authorization Rules** | Methods (`permission()`) where groups are assigned specific actions (READ, WRITE, DELETE) for objects, fields, or collections |
| **Custom Logic** | Developers can write custom authorization logic for complex rules like ownership or hierarchical access |
Unify's permission system is flexible, allowing developers to define, check, and enforce rules throughout the application — from **UI visibility** to **backend operations** like SQL queries and method executions.

64
Property_Binding.md Normal file
View File

@@ -0,0 +1,64 @@
### Automatic Rendering and Property Binding in Unify
In Unify, you **define properties** on your objects, and the framework automatically **creates the associated DOM elements**, applies **styles**, and **binds the properties** without you needing to manually manipulate the DOM. This approach significantly reduces boilerplate code, making it easier and faster to build dynamic user interfaces.
#### Key Concepts:
1. **Automatic Element Creation**:
When you define a Unify object, like a `panel`, the framework automatically generates the corresponding HTML element for you. You don't need to manually call `document.createElement()` or manage the element's attributes.
2. **Property Binding**:
The properties you define (such as `height`, `width`, or `text`) are automatically reflected in the rendered DOM element. For instance, if you set the `height` property of a `panel` object, Unify ensures that the corresponding HTML element's height is updated accordingly.
3. **No Need for Render Methods**:
Unlike traditional frameworks where you need to explicitly define a `render()` method to apply changes to the DOM, Unify handles this for you. Simply updating the properties of a Unify object triggers the necessary updates in the DOM without needing extra code.
4. **Dynamic Updates**:
Whenever a property is changed, such as adjusting the `height` or `text` of an element, Unify automatically re-renders the element to reflect these changes. There's no need to manually call DOM manipulation methods like `element.style.height` or `element.innerText`.
#### Example:
Heres an example demonstrating how Unify makes it easy to manage UI elements:
```js
class panel {
// Define initial properties
height = 100;
width = 200;
margin = 20;
text = "Initial Text";
// Modify properties dynamically
click() {
this.height = 300; // Change height
this.width = 400; // Change width
this.text = "Updated Text"; // Change text content
}
}
// Create an instance of the panel
const myPanel = new panel();
```
### How It Works:
1. **No Manual DOM Updates**: You dont need to manually create elements or apply styles. Unify automatically creates an HTML element for your `panel` and applies the `height`, `width`, and `text` properties to it.
2. **Automatic Synchronization**: When you modify properties like `height`, `width`, or `text`, Unify ensures that the corresponding DOM element is updated to reflect these changes without requiring additional code.
3. **Simplified UI Development**: This approach makes it easier to build dynamic, interactive UIs. You simply update the objects properties, and Unify handles the underlying DOM updates.
4. **Declarative Approach**: By defining properties on objects, you declaratively specify the state of your UI, and Unify takes care of the rendering and synchronization for you.
### Benefits:
* **Less Boilerplate Code**: Since Unify automatically binds properties to DOM elements, there's no need for repetitive code to manually update styles or text.
* **Faster Development**: By removing the need for manual DOM manipulation, you can focus on defining the state and behavior of your components.
* **Automatic Synchronization**: Changes to object properties are automatically reflected in the UI without any extra code to update the DOM.
---
This approach streamlines the process of building dynamic UIs and ensures that your objects and the DOM are always in sync, reducing the likelihood of errors and improving maintainability. With Unify, you focus on defining the state of your UI, and the framework takes care of the rendering for you.

214
Readme.md Normal file
View File

@@ -0,0 +1,214 @@
# Unify Developer Manual
### Build full applications with a single, unified codebase.
Unify is a full-stack application framework designed to remove the accidental complexity of modern web development. Instead of hand-wiring HTTP requests, state management, SQL queries, UI updates, permissions, and caching — Unify turns each **domain object** into a **live application object** that exists simultaneously on the server and client.
You write your application *once*.
Unify takes care of syncing it everywhere it needs to be.
---
## Why Unify?
Most web apps are assembled from disconnected technologies:
React + REST + ORM + SQL + State Manager + WebSockets + UI Framework + Access Control…
Each layer requires boilerplate to keep everything in sync:
* serialize → send → parse → update → re-render → validate permissions
* write SQL + write endpoints + write frontend state logic for the same entity
Unify eliminates these seams.
> **One class = data + logic + UI + permissions + real-time behavior**
The framework:
* turns your class into a database table (server)
* generates UI components from that class (client)
* syncs all changes via a structured WebSocket protocol
* applies permissions automatically based on user role
* integrates filtering, joins, pagination at the SQL level
* caches and updates only changed fields
You focus on the domain model.
Unify handles the rest.
---
### Why Unify Is AI-Optimized
Unify applications are self-describing graphs.
The object structure defines:
UI layout
Backend relationships
Permissions
Sync rules
Navigation
A human developer may lose track of where objects live in the graph.
But AI can:
infer full graph topology
find optimal traversal paths
safely modify relationships
rewrite UI interactions without breaking structure
This means Unify enables:
AI-generated UI actions
AI-guided refactoring
AI-assisted CRUD creation
Automated join discovery
Intelligent navigation handling
## What does development feel like?
Example: define a `News` class
```javascript
export default class news extends table {
title = new text("Title");
body = new text("Message");
price = new number("Price");
permission() {
this.allow(groups.visitor, "READ");
this.allow(groups.admin, "WRITE");
}
}
```
And a list view:
```javascript
export default class newsListTable extends gridView {
header = new newsListTableHeader();
body = new newsListTableBody(newsListItem, new collection(news));
}
```
You dont manually:
* write SQL queries
* define REST endpoints
* build WebSocket handlers
* implement CRUD actions
* code client state syncing
* generate UI markup
Unify does all of that from your object graph.
---
## Architecture: Whats inside
| Layer | Responsibility |
| ---------------------- | ------------------------------------------ |
| **Domain Objects** | Single class shared by server + client |
| **Render Collections** | Auto-loaded lists with pagination + search |
| **Table Controller** | Database operations + joins |
| **Socket Controller** | Real-time communication layer |
| **UI Runtime** | Live elements, animations, events |
| **Permission Engine** | Automatic security per object/action |
This creates a **Behavioral ORM**:
* objects contain their own server logic
* queries are automatically composed from UI actions
* users see only what they are permitted to interact with
---
## Why does this matter?
Modern software complexity isnt caused by features — its caused by **glue code**.
For every feature, you implement it 36 times: backend models, database schema, API routes, frontend state, UI bindings…
Unify collapses those layers into **one source of truth**.
The result:
* drastically fewer bugs
* ultra-fast iteration cycles
* real-time UX without extra work
* secure-by-construction access rules
* smaller teams can ship full apps
---
## Current focus
The goal of this release is to give developers a working demonstration of:
* full-stack sync with no REST boilerplate
* real-time UI updates
* built-in permissions
* SQL searching / joining from the UI
* cross-platform dynamic UI rendering (Windows / Mac / Web)
The included **News Application** shows:
* search/filter with SQL operators (`LIKE`, `OR`, `>=`)
* item detail transitions + CRUD dialogs
* login + role-based access
* pagination
* dynamic view/layout behavior
---
## Who is Unify for?
✔ Full-stack developers
✔ Product developers who hate glue code
✔ Teams who want to ship faster
✔ Anyone who believes software should be simpler
---
## Status
Unify is actively developed and evolving.
Contributions, feedback, and architectural discussion are welcome.
> Help define the future of unified application development.
---
## Get started
The source includes:
* `unify_source/` the core framework
* `unify_application/` working reference application
Clone and run the app to see the architecture in motion:
```
git clone https://.../unify
cd unify_application
npm install
npm start
```
Documentation and complete API references are coming.

212
Rendering.md Normal file
View File

@@ -0,0 +1,212 @@
# Rendering Data: Render Collections, Lists & Pagination
In Unify, data-driven interfaces, such as tables, lists, or collections, are rendered through a powerful concept: **Render Collections**.
A **Render Collection** is a UI component that displays rows of data, with a complete lifecycle from querying the backend to rendering the results, all while maintaining a declarative, JavaScript-centric approach.
## 1. Define a Render Collection
A render collection consists of two key components:
* **The Row Renderer**: A component that defines how each row is visually rendered.
* **The Collection**: A component that defines the data source and binds to a backend Table (like `news`, `users`, etc.).
### Example: Defining a Render Collection
```js
export default class newsListTable extends gridView {
header = new newsListTableHeader();
body = new newsListTableBody(
newsListItem, // Row Renderer
new collection(news) // Backend Data Binding
);
}
```
In this example:
* **`newsListItem`** is the row component used to render individual items.
* **`new collection(news)`** binds to the `news` Table on the backend, providing the data to be displayed.
### How it works:
* The **Collection** defines the data.
* The **Row Renderer** defines how to display each individual row.
---
## 2. Syncing Data — Client-Server Integration
Unify makes sure the **data remains synced** across the client and the backend. You can fetch data on-demand and refresh it easily with the `.sync()` method:
```js
await this.body.sync();
```
When the data is synced, the following happens:
1. The query is sent to the server (via `node async filterSearch()`).
2. The server executes the query and returns the rows.
3. The rows are parsed and displayed in the UI via the **Row Renderer**.
If the collection is already **pre-bound** to a table with an `id`, calling `.sync()` will also refresh that specific objects details.
---
## 3. Pagination — Developer Controlled
Pagination in Unify is **developer-controlled**. Unlike typical frameworks that handle pagination automatically, you have full control over how the **limit**, **offset**, and **searching** are applied.
### Pagination Example:
```js
async update(updatePagination = true) {
if (updatePagination) {
this.page = 0;
}
// Filtering and pagination logic
this.numberOfPages = await this.filterSearch(
this.searchType,
this.searchTerm,
this.sort,
this.direction,
this.limit,
this.page
);
await this.sync();
if (updatePagination) {
this.parents("newsItemPage").tableControl.pagination.create();
}
}
```
In this case:
1. **`filterSearch()`** constructs the filter for the query and is called to fetch the rows.
2. After filtering, **pagination** is managed manually.
3. **`sync()`** then updates the UI with the results.
This gives you control over **pagination logic** and how the list is managed.
---
## 4. Filtering and Searching — Declarative Querying
In Unify, **filtering and searching** are handled using **logical expressions** that are parsed on the server side. You construct the queries using Unify's SQL helpers:
```js
import OR from '/unify/sql/OR.js';
import LIKE from '/unify/sql/LIKE.js';
import GREATER from '/unify/sql/GREATER_OR_EQUAL.js';
import SMALLER from '/unify/sql/SMALLER_OR_EQUAL.js';
node async filterSearch(searchType, searchTerm, order, direction, limit, page) {
const filter = this.getFilter();
switch (searchType) {
case 0:
filter.search = OR(
OR(LIKE(filter.title, searchTerm), LIKE(filter.comments.body, searchTerm)),
LIKE(filter.body, searchTerm)
);
break;
case 1:
filter.search = GREATER(filter.price, searchTerm);
break;
case 2:
filter.search = SMALLER(filter.price, searchTerm);
break;
}
if (!searchTerm) filter.search = false;
filter.order = filter[order];
filter.direction = direction === "desc" ? "desc" : "asc";
filter.limit = parseInt(limit);
filter.from = parseInt(page * limit);
await this.get(); // Fetch results
const numberOfPages = Math.ceil(this.rows.length / limit);
return numberOfPages;
}
```
### Key Points:
* **Logical operators** like `OR`, `AND`, and `LIKE` are used to build declarative filters.
* Filters are executed directly **on the server**. The objects are **parsed as-is**, without the need for string-based queries or manual SQL building.
* **Pagination and sorting** logic is fully under the developers control.
The **query-building** helpers (`LIKE`, `GREATER`, `SMALLER`) are parsed directly by the server. They allow you to declaratively specify complex queries in JavaScript, all while Unify handles the logic and results.
---
## 5. Permissions — Controlling Access to Data
Just like Unify Table objects, **Render Collections** have a permission system that controls who can **read**, **write**, or **delete** data. For example:
```js
permission() {
this.allow(groups.visitor, "READ");
this.allow(groups.member, "READ");
this.allow(groups.admin, "READ");
}
```
Permissions can control:
* Which rows are visible to different user roles
* Which fields inside those rows are visible or editable
Its important to note that **filtering** does not automatically enforce permissions. You must explicitly **validate** access rights to each table, column, or row.
For example, before executing an action like `deleteRow()`, you must check the users permissions:
```js
node async deleteRow(client) {
const user = client.user;
if (!user.hasPermission("DELETE_NEWS")) {
throw new Error("Permission denied");
}
this.delete();
}
```
---
## 6. Lifecycle Summary
| Action | Happens Where | Implemented by |
| --------------------------- | ------------------------ | -------------- |
| Configure UI of list | Client | Developer |
| Construct query logic | Shared (Client + Server) | Developer |
| Execute SQL on server | Server | Unify |
| Deserialize objects | Server → Client | Unify |
| Render rows in the UI | Client | Unify |
| Handle permissions for rows | Server | Developer |
Unify abstracts away complex data handling. The **UI** remains declarative and **directly synced** with the **backend**, ensuring **zero glue code** between database and frontend.
---
## Conclusion
Unifys **Render Collections** provide a simple, declarative way to build **dynamic, paginated, filtered lists**:
1. The **backend logic** remains completely abstracted (no REST APIs, no ORM configuration).
2. The **client UI** renders the data based on **filter and query objects**.
3. The **permissions and security** of the data are automatically respected with clear separation between **frontend** and **backend** logic.
The system gives you **total control** over pagination, sorting, searching, and permissions, while Unify handles the **syncing**, **UI updates**, and **backend query execution** for you.

163
Tables.md Normal file
View File

@@ -0,0 +1,163 @@
Here is a fully rewritten version of the entire **Tables + Columns + Floating Mapping** section — accurate, simplified, and without over-emphasizing the “floating” term, while still explaining why it matters.
---
## Tables & Columns
### Automatic SQL Mapping from UI Components
In Unify, **your UI defines your data model**.
Every data entity in an application is a **table object**:
```js
import table from '/unify/table.js';
export default class user extends table {
username = new username(); // column
email = new email(); // column
groups = new groups(); // relation
}
```
Any child object that extends `column` is automatically treated as a **database column** belonging to its nearest table ancestor.
There is no need to write schemas, migrations, or model config files.
Unify inspects the structure at runtime and understands:
* Table name → class name (`user`)
* Column names → field names (`username`, `email`, …)
* Relationships → nested collections
* Permissions → defined on the table
No duplication. No ORM setup.
Your UI code **is** your data model.
---
### Columns Can Live Anywhere in the UI Tree
Columns do not need to be direct children of the table.
You can design your UI freely:
```js
export default class userEditPanel extends panel {
accountPanel = new accountPanel();
contactPanel = new contactPanel();
}
class accountPanel extends panel {
username = new username(); // column
}
class contactPanel extends panel {
email = new email(); // column
}
```
Unify automatically discovers these fields because they appear **under a table instance** in the object tree.
So you are free to:
* Split a screen into panels
* Reuse complex component hierarchies
* Move UI structure without refactoring SQL code
This enables **UI-first design** without breaking the data layer.
---
### CRUD Without CRUD Code
Unify provides two primary data operations:
| Action | Developer code | Result |
| ------ | ------------------- | ----------------------------------- |
| Save | `await user.save()` | INSERT or UPDATE depending on `id` |
| Load | `await user.sync()` | SELECT + populate all child columns |
Example: Editing a profile
```js
async click() {
const form = this.parents('userEdit');
await form.save(); // automatically writes username & email
form.hide();
}
```
And for loading:
```js
user.id = 42;
await user.sync(); // loads username/email into the UI automatically
```
No setters.
No binding boilerplate.
No DTOs.
No form libraries.
The table object is both:
* Data source
* UI container
* Binding layer
---
### Why This Matters
| Traditional Full-Stack | With Unify |
| --------------------------- | -------------------------- |
| Write database schema | Skip — inferred |
| Write backend model | Skip — inferred |
| Write API controllers | Skip — RPC auto-generation |
| Write validation logic | Built into column objects |
| Build UI → manually connect | UI *is* the data model |
| Keep everything in sync | `.sync()` + `.save()` |
This eliminates the friction between backend + frontend and replaces it with one unified codebase.
---
### Minimal Mental Model
> A **table** object represents a database row.
> Any **column** object inside that tables UI subtree automatically becomes a database column.
> `sync()` loads data into the UI.
> `save()` writes UI data back to SQL.
Nothing more is needed to build and ship real applications.
---
### Example Recap
```js
user.id = 7;
await user.sync(); // load existing row → UI updates
user.username.value = "kaj";
await user.save(); // update DB row with new values
```
One line to fetch, one line to commit.
---
If you'd like, the next chapter can flow directly into **Render Collections & Lists**, since they demonstrate the same philosophy at scale:
* UI defines queries (pagination, filtering)
* Changes propagate automatically across the app
Should I continue with:
A) Render Collections + Filtering/Searching
B) Permissions (groups, scopes, READ/WRITE/DELETE)
C) Routing + Animated Page State
D) Server RPC Communication Model (socket commands)
Just tell me which one comes next.

65
Why.md Normal file
View File

@@ -0,0 +1,65 @@
### 1. **Full-Stack Integration with Minimal Effort**
Unify offers a **high-level abstraction** over traditional full-stack frameworks, combining the backend (data models, SQL queries, server-side logic) and frontend (UI rendering, state management, animations) into a single cohesive system. It drastically reduces the need for **manual synchronization** between layers, which is typically a source of complexity in traditional architectures.
**Value Add**: By using Unify, you're gaining expertise in building applications that **automatically sync** across client and server, without needing to write separate API routes, manage frontend state, or even handle SQL queries manually.
### 2. **AI Optimization Potential**
Unify's **self-describing object graph** enables AI to generate code for UI actions, refactoring, CRUD generation, and more. This opens up the potential for **AI-assisted development**, making tasks like optimizing UI behavior, managing state, and writing backend logic much faster.
**Value Add**: Youre working with a **forward-thinking framework** that embraces AI optimization, placing you at the forefront of innovation in the full-stack development space.
### 3. **Declarative Approach to Data and UI**
Unify turns **data models into UI components**. You define your business logic in terms of **classes** and **objects**, and Unify handles the database queries, API calls, state management, and UI bindings.
**Value Add**: This **declarative model** significantly reduces **boilerplate code** and streamlines the development process. You are not manually wiring complex data flows, which means you can focus more on solving the core problem rather than dealing with infrastructure and repeated patterns of implementation.
### 4. **Real-Time Sync and Permissions Management**
Unify automatically syncs data between the client and the server, ensuring that the state is always up-to-date across all layers of the application. It also applies **permissions automatically** based on user roles and object-level access control, ensuring **security** is built into the framework rather than added as an afterthought.
**Value Add**: You get **real-time syncing** and **permissions enforcement** with minimal effort, which are traditionally complex features to implement. This **reduces the risk of security vulnerabilities** and improves the consistency of user data across applications.
### 5. **Automatic Query Composition**
Unifys system allows **SQL queries to be generated automatically** from UI interactions and object manipulations. Filtering, pagination, and joins are built-in features, which means you don't have to write SQL manually for each interaction. You simply define what you want the data to look like, and Unify handles the query generation.
**Value Add**: This **eliminates SQL boilerplate** and query-building code, helping you rapidly prototype applications with complex data requirements, saving you significant time on backend development.
### 6. **Flexible and Scalable UI with Floating Columns**
The framework allows you to **flexibly design the UI** without worrying about how it will impact the underlying database model. With the concept of **floating columns**, Unify offers **dynamic UI management**, where objects (e.g., panels, tables) can be nested freely, with no disruption to backend data structures.
**Value Add**: This **enhances your design flexibility** and allows you to **focus on user experience** without the constraints of having to directly map UI components to your database schema. Its a powerful feature for building **scalable, maintainable UIs**.
### 7. **Minimal Glue Code and Increased Efficiency**
Unify eliminates the need for traditional **"glue code"** that connects various parts of an app: backend models, database schema, API routes, and frontend state. Instead, it relies on **automated syncing**, reducing duplication and the potential for bugs.
**Value Add**: By cutting out this **manual glue code**, you can ship **full applications faster** and with fewer opportunities for errors. Your workflow becomes more **efficient**, focusing solely on defining business logic and UI components.
---
### How does it add value to your CV?
* **Innovative Technology**: Unify is a highly **innovative framework** that addresses many of the challenges full-stack developers face, such as synchronization, permission management, and data handling. Being involved with a framework like this demonstrates your ability to work with cutting-edge technology.
* **Problem-Solving**: Working with Unify showcases your **problem-solving skills**. You are tackling the problem of **redundant code and complexity** in a novel way, leveraging the power of abstraction to create a more elegant solution for modern web development.
* **Future-Proofing**: By working with a framework that has the potential to integrate **AI-assisted development**, youre positioning yourself as someone who is not just keeping up with modern trends but also preparing for **future technologies**.
* **Reduced Development Time**: Your experience with Unify shows that you can develop and ship complex applications quickly and efficiently, as the framework automates much of the **boilerplate coding** involved in backend and frontend development.
* **Full-Stack Expertise**: Unify allows you to work across the entire stack, from database to UI to real-time updates, making you a **well-rounded full-stack developer**. This skill is highly valuable in todays job market.
* **Security Awareness**: The **automatic permission system** in Unify indicates that youre mindful of security concerns and can develop applications with **built-in access control**.
---
### Conclusion
Unify, with its **automatic syncing**, **AI optimization potential**, and **declarative approach** to building applications, is an advanced framework that offers significant time-saving benefits. By adding this to your CV, you're showcasing your ability to work with **cutting-edge technologies** that address modern full-stack development challenges.
If youre comfortable with the frameworks complexity and confident in its capabilities, it would be **highly beneficial** to include it on your CV, especially if you're targeting positions where efficiency, innovation, and full-stack expertise are valued.

View File

@@ -0,0 +1,69 @@
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
create() {
this.animation = this.createAnimation("backgroundAnimation");
var key = this.animation.createKeyFrame( 0 );
key.setProperty( "background", "#03a9f4" );
var key = this.animation.createKeyFrame( 40 );
key.setProperty( "background", "#a6e22e" );
var key = this.animation.createKeyFrame( 70 );
key.setProperty( "background", "#f92672" );
var key = this.animation.createKeyFrame( 100 );
key.setProperty( "background", "#03a9f4" );
}
async click() {
this.animation.play("2s");
}
}
class rowLabel extends label{
flex = "1";
}
export default class row extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("Background color");
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,79 @@
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
marginTop = 12;
marginLeft = 12;
create() {
this.moveAnimation = this.createAnimation("moveAnimation");
var key = this.moveAnimation.createKeyFrame( 0 );
key.setProperty( "margin-right", 12 );
var key = this.moveAnimation.createKeyFrame( 20 );
//key.setProperty( "margin-top", 200 );
key.setProperty( "margin-right", 40 );
var key = this.moveAnimation.createKeyFrame( 30 );
//key.setProperty( "margin-top", 500 );
key.setProperty( "margin-right", 120 );
var key = this.moveAnimation.createKeyFrame( 50 );
key.setProperty( "margin-top", 200 );
var key = this.moveAnimation.createKeyFrame( 100 );
//key.setProperty( "margin-top", 0 );
}
async click() {
this.moveAnimation.play("2s");
}
}
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
class rowLabel extends label{
flex = "1";
}
export default class moveRow extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("Move");
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,50 @@
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
time = 0;
render( ) {
this.time++;
this.width = 100 + ( Math.cos( this.time / 100 ) * 100 );
this.height = 100 + ( Math.sin( this.time / 100 ) * 100 );
}
}
class rowLabel extends label{
flex = "1";
}
export default class row extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("RenderLoop");
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,188 @@
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
import button from '/elements/button.js';
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
create() {
this.animation = this.createAnimation("reverseAnimation");
/*
var key = this.animation.createKeyFrame( 0 );
key.setProperty( "rotate", "0deg" );
var key = this.animation.createKeyFrame( 20 );
key.setProperty( "rotate", "60deg" );
var key = this.animation.createKeyFrame( 60 );
key.setProperty( "rotate", "120deg" );
var key = this.animation.createKeyFrame( 100 );
key.setProperty( "rotate", "0deg" );
*/
var key = this.animation.createKeyFrame( 0 );
key.setProperty( "rotate", "0deg" );
var key = this.animation.createKeyFrame( 100 );
key.setProperty( "rotate", "360deg" );
this.animation.duration = "2s"
this.animation.iterationCount = "infinite"
this.animation.fillMode = "forwards"
}
async mouseover() {
//this.animation.play();
}
async mouseleave() {
//this.animation.pause();
}
}
class forwardButton extends button{
text = "Direction Forward";
click() {
var animation = this.parent.parent.animationBlock.animation;
animation.direction = "normal"
}
}
class backwardButton extends button{
text = "Direction Backward";
click() {
var animation = this.parent.parent.animationBlock.animation;
animation.direction = "reverse"
}
}
class pauseButton extends button{
text = "Pause";
click() {
var animation = this.parent.parent.animationBlock.animation;
animation.pause();
}
}
class playButton extends button{
text = "Play";
click() {
var animation = this.parent.parent.animationBlock.animation;
animation.play();
}
}
class stopButton extends button{
text = "Stop";
click() {
var animation = this.parent.parent.animationBlock.animation;
animation.stop();
}
}
class rowLabel extends label{
flex = "1";
}
class buttons{
playButton = new playButton();
pauseButton = new pauseButton();
stopButton = new stopButton();
forwardButton = new forwardButton();
backwardButton = new backwardButton();
flexDirection = "column"
}
export default class row extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("Reverse");
buttons = new buttons();
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,67 @@
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
create() {
this.animation = this.createAnimation("rotateAnimation");
var key = this.animation.createKeyFrame( 0 );
key.setProperty( "rotate", "0deg" );
var key = this.animation.createKeyFrame( 20 );
key.setProperty( "rotate", "60deg" );
var key = this.animation.createKeyFrame( 60 );
key.setProperty( "rotate", "120deg" );
var key = this.animation.createKeyFrame( 100 );
key.setProperty( "rotate", "0deg" );
}
async click() {
this.animation.play("2s");
}
}
class rowLabel extends label{
flex = "1";
}
export default class row extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("Rotate");
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,145 @@
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
zIndex = 4;
create() {
this.rotateAnimation = this.createAnimation("rotateAnimation");
var key = this.rotateAnimation.createKeyFrame( 0 );
key.setProperty( "rotate", "0deg" );
var key = this.rotateAnimation.createKeyFrame( 20 );
key.setProperty( "rotate", "60deg" );
var key = this.rotateAnimation.createKeyFrame( 60 );
key.setProperty( "rotate", "120deg" );
var key = this.rotateAnimation.createKeyFrame( 100 );
key.setProperty( "rotate", "0deg" );
this.moveAnimation = this.createAnimation("moveAnimation");
var key = this.moveAnimation.createKeyFrame( 0 );
key.setProperty( "margin-right", 12 );
var key = this.moveAnimation.createKeyFrame( 20 );
//key.setProperty( "margin-top", 200 );
key.setProperty( "margin-right", 40 );
var key = this.moveAnimation.createKeyFrame( 30 );
//key.setProperty( "margin-top", 500 );
key.setProperty( "margin-right", 120 );
var key = this.moveAnimation.createKeyFrame( 50 );
key.setProperty( "margin-top", 200 );
var key = this.moveAnimation.createKeyFrame( 100 );
//key.setProperty( "margin-top", 0 );
this.backgroundAnimation = this.createAnimation("backgroundAnimation");
var key = this.backgroundAnimation.createKeyFrame( 0 );
key.setProperty( "background", "#03a9f4" );
var key = this.backgroundAnimation.createKeyFrame( 40 );
key.setProperty( "background", "#a6e22e" );
var key = this.backgroundAnimation.createKeyFrame( 70 );
key.setProperty( "background", "#f92672" );
var key = this.backgroundAnimation.createKeyFrame( 100 );
key.setProperty( "background", "#03a9f4" );
}
async click() {
this.text = "Rotating and moving.";
this.rotateAnimation.play("2s");
await this.moveAnimation.play("3s");
this.text = "Changing background color.";
await this.backgroundAnimation.play("2s");
this.text = "Animation is done."
}
}
class rowLabel extends label{
flex = "1";
}
export default class row extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("Rotate + Move + Background");
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,67 @@
import panelRow from '/elements/panel/row.js';
import label from '/elements/label.js';
class animationBlock{
width = 100;
height = 100;
margin = 20;
background = "#03a9f4";
create() {
this.animation = this.createAnimation("rotateAnimation");
var key = this.animation.createKeyFrame( 0 );
key.setProperty( "skewX", "0deg" );
var key = this.animation.createKeyFrame( 20 );
key.setProperty( "skewX", "14deg" );
var key = this.animation.createKeyFrame( 60 );
key.setProperty( "skewX", "52deg" );
var key = this.animation.createKeyFrame( 100 );
key.setProperty( "skewX", "0deg" );
}
async click() {
this.animation.play("2s");
}
}
class rowLabel extends label{
flex = "1";
}
export default class row extends panelRow{
boxWidth = "95%"
rowLabel = new rowLabel("skewX");
animationBlock = new animationBlock();
}

View File

@@ -0,0 +1,95 @@
import moveAnimation from "./animation.move.js";
import rotateAnimation from "./animation.rotate.js";
import backgroundColor from "./animation.color.js";
import skewX from "./animation.skewX.js";
import rotateMoveColor from "./animation.rotateMoveColor.js";
import reverse from "./animation.reverse.js";
import render from "./animation.render.js";
export default class animations{
overflowY = "auto"
height = 600;
width = "auto"
flexDirection = "column";
scrollbarWidth = "6px";
scrollbarTrackBackground = "#1c1d1e";
scrollbarThumbBackground = "#404040"
scrollbarThumbBorderRadius = "4px"
scrollbarThumbHoverBackground = "grey";
#ifdef MACOS
#ifdef DARK
background = "#282828";
#endif
#ifdef LIGHT
background = "#fdfdfd";
#endif
#endif
#ifdef WINDOWS
#ifdef DARK
background = "#202020cc";
#endif
#ifdef LIGHT
background = "rgb(255 255 255 / 75%)";
#endif
#endif
layers = 1;
// height = "-webkit-fill-available";
padding = 20;
// width = "100%"
moveAnimation = new moveAnimation();
rotateAnimation = new rotateAnimation();
backgroundColor = new backgroundColor();
skewX = new skewX();
rotateMoveColor = new rotateMoveColor();
reverse = new reverse();
render = new render();
}

View File

@@ -0,0 +1,183 @@
import minimizeButton from './minimizeButton.js';
import leftSide from './leftSide/leftSide.js';
import rightSide from './rightSide/rightSide.js';
import document from '/unify/document.js';
import vector2 from '/unify/math/vector2.js';
import flexbox from '/elements/flexbox.js';
import frostedGlass from '/elements/window/frostedGlass.js';
import draggable from '/elements/window/draggable.js';
export default class application extends frostedGlass, flexbox, draggable{
// Children
minimizeButton = new minimizeButton();
leftSide = new leftSide();
rightSide = new rightSide();
// Environment
mode = "development"
os = "Windows";
device = "Pc";
tint = "Dark";
loadThemes = true;
maxClients = 1000;
cacheBuildSpeed = 4;
maxClusters = 1;
//serverAddress = "192.168.178.15";
// Styling
position = "absolute";
borderRadius = 12;
boxBackgroundSize = "1000px 1000px";
boxTransition = "background-image 0.1s ease-in-out";
boxHeight = "100vh";
position = "absolute";
#ifdef ANDROID
flexDirection = "column";
#else
flexDirection = "row";
#endif
// Pragma's
#ifdef ANDROID
lastPosition = new vector2( 0, 0 );
fontFamily = "android";
transform = "translate(0px, 0px)";
#endif
#ifdef WINDOWS
fontFamily = "SegoeUI";
#ifdef DARK
//boxBackgroundImage = "url('/assets/images/wallpapers/windows/light/2048.png')";
//backgroundImage = "url('/assets/images/wallpapers/windows/light/blur_1024.jpg')";
#endif
#ifdef LIGHT
//boxBackgroundImage = "url('/assets/images/wallpapers/windows/light/2048.jpg')";
//backgroundImage = "url('/assets/images/wallpapers/windows/light/blur_1024.jpg')";
#endif
border = "1px solid rgb(65 84 118 / 32%)";
#endif
#ifdef MACOS
fontFamily = "Inter";
#ifdef DARK
//boxBackgroundImage = "url('/assets/images/wallpapers/ventura/dark/dark.jpg')";
//backgroundImage = "url('/assets/images/wallpapers/ventura/dark/darkBlur.jpg')";
border = "1px solid #8f8f8f59";
outline = "1px solid black";
#endif
#ifdef LIGHT
//boxBackgroundImage = "url('/assets/images/wallpapers/ventura/light/light.jpg')";
//backgroundImage = "url('/assets/images/wallpapers/ventura/light/lightBlur.jpg')";
#endif
#endif
// Methods
afterLoad() {
console.log( "loaded application", this );
this.centerObject();
}
centerObject() {
var domWindow = document.defaultView;
this.windowHeight = domWindow.innerHeight;
this.windowWidth = domWindow.innerWidth;
var boundBox = this.defaultElement.getBoundingClientRect();
var width = boundBox.width;
var height = boundBox.height;
var x = this.windowWidth / 2 - ( width / 2 );
var y = this.windowHeight / 2 - ( height / 2 );
this.lastPosition = new vector2( Math.round( x ), Math.round( y ) );
}
click() {
this.boxShadow = "1px 1px 3px 0px #00000054"
}
}

View File

@@ -0,0 +1,11 @@
import column from '/unify/column.js';
export default class body extends column {
}

View File

@@ -0,0 +1,22 @@
import table from '/unify/table.js';
import user from '/user/user.js';
import body from './comment.body.js';
import title from './comment.title.js';
export default class comment extends table{
author = new user();
title = new title();
body = new body();
flexDirection = "column";
}

View File

@@ -0,0 +1,40 @@
import column from '/unify/column.js';
export default class title extends column {
padding = 20;
color = "black";
label = "title";
useCustomElement = true;
async keyup( event ){
this.value = event.target.value;
this.animate(150, 400, function( value ){
this.height = value;
})
var result = await this.socketManager.get( "column", "update", this, "keyup" );
}
serverKeyup( object ) {
this.value = object.value;
}
}

View File

@@ -0,0 +1,48 @@
import renderCollection from '/unify/renderCollection.js';
import groups from '/user/group/user.group.permission.js';
import OR from '/unify/sql/OR.js';
import AND from '/unify/sql/AND.js';
import LIKE from '/unify/sql/LIKE.js';
export default class commentsMessages extends renderCollection {
flexFlow = "column";
direction = "desc";
width = "-webkit-fill-available";
marginTop = 20;
debug = true;
node async search( value ) {
var filter = this.getFilter();
filter.search = OR( LIKE( filter.body, value ), LIKE( filter.title, value ) );
filter.direction = "desc";
}
permission() {
this.allow( groups.visitor, "READ" );
this.allow( groups.member, "READ" );
this.allow( groups.admin, "READ" );
}
}

View File

@@ -0,0 +1,8 @@
import user from '/user/user.js';
export default class commentEditAuthor extends user{
display = "none";
}

View File

@@ -0,0 +1,16 @@
import commentBody from '../comment.body.js';
import textarea from '/elements/textarea.js';
export default class commentEditBody extends commentBody, textarea{
useCustomElement = true;
height = "97px";
placeholder = "Message";
}

View File

@@ -0,0 +1,103 @@
import comment from '../comment.js';
import saveButton from './comment.saveButton.js';
import userLabel from './comment.userLabel.js';
import commentEditTitle from './comment.create.title.js';
import commentEditBody from './comment.create.body.js';
import commentEditAuthor from './comment.create.author.js';
import header from '/elements/header.js';
import collection from '/unify/collection.js';
import groups from '/user/group/user.group.permission.js';
export default class createComment extends comment{
display = "flex";
body = new commentEditBody();
saveButton = new saveButton();
title = false;
author = this.user; // bug destroys the permission system
#ifdef WINDOWS
#ifdef DARK
#endif
#ifdef LIGHT
#endif
#endif
#ifdef MACOS
#ifdef DARK
background = "#00000042";
#endif
#ifdef LIGHT
background = "#ffffffd1";
#endif
#endif
width = "50vw";
debug = true;
width = "100%";
marginTop = 40;
async create() {
this.body.value = "";
this.setID( false );
}
disableWRITE() {
this.hide();
}
enableWRITE() {
this.show();
}
permission() {
this.allow( groups.member, "WRITE" );
this.allow( groups.admin, "WRITE" );
}
}

View File

@@ -0,0 +1,8 @@
import commentTitle from '../comment.title.js';
export default class commentEditTitle extends commentTitle{
}

View File

@@ -0,0 +1,29 @@
import button from '/elements/button.js';
import tools from '/unify/tools.js';
export default class saveCommentButton extends button {
label = "Create comment";
async click( event ){
var result = await this.socketManager.get( "table", "save", this.parent );
this.parent.create();
await this.parent.parent.commentsMessages.sync();
this.parent.parent.customElement.scrollTo( 0, this.parent.parent.customElement.scrollHeight);
console.log("laatste", this.parent.parent.customElement.scrollHeight);
}
}

View File

@@ -0,0 +1,38 @@
import input from '/elements/input.js';
export default class userLabel extends input{
float = "right";
useCustomElement = false;
height = 20;
float = "right";
marginLeft = 100;
marginTop = 20;
setAuthor( author ) {
if( author.username ) {
this.value = "author: " + author.username.value;
}
}
create() {
var author = this.parent.parent.author;
this.setAuthor( author );
}
}

View File

@@ -0,0 +1,37 @@
import button from '/elements/button.js';
export default class deleteButton extends button {
label = "Delete";
#ifdef ANDROID
fontSize = 12;
width = "auto"
height = "auto"
#endif
async click() {
var sure = confirm("Are you sure you want to delete this Post");
if( sure ) {
this.parent.parent.delete();
this.parent.parent.remove();
}
}
}

View File

@@ -0,0 +1,10 @@
import user from '/user/user.js';
export default class commentEditAuthor extends user{
display = "none";
}

View File

@@ -0,0 +1,109 @@
import commentBody from '../comment.body.js';
import textarea from '/elements/textarea.js';
import document from '/unify/document.js';
import flexbox from '/elements/flexbox.js';
export default class commentEditBody extends commentBody, flexbox{
customElement = document.createElement("textarea");
useCustomElement = true;
width = "-webkit-fill-available"
padding = 20;
#ifdef MACOS
#ifdef LIGHT
background = "white";
color = "black";
borderRadius = 12;
margin = 6;
#endif
#ifdef DARK
background = "#282828!important";
//color = "white";
#endif
#endif
#ifdef WINDOWS
#ifdef LIGHT
background = "none";
color = "black";
#endif
#ifdef DARK
background = "#202020cc";
borderRadius = 12;
#endif
margin = 16;
#endif
async keyup( event ){
this.value = event.target.value;
var result = await this.socketManager.get( "column", "update", this, "keyup" );
}
create() {
this.deactivateTextarea()
}
activateTextarea() {
this.useCustomElement = true;
}
deactivateTextarea() {
this.useCustomElement = false;
}
useCustomElement = false;
fontSize = 14;
//color = "red";
}

View File

@@ -0,0 +1,127 @@
import comment from '../comment.js';
import saveButton from './comment.saveButton.js';
import commentEditTitle from './comment.edit.title.js';
import commentEditBody from './comment.edit.body.js';
import commentEditAuthor from './comment.edit.author.js';
import header from '/elements/header.js';
import collection from '/unify/collection.js';
import userLabel from './comment.userLabel.js';
import deleteButton from './comment.deleteButton.js';
import editButton from './comment.editButton.js';
import information from './comment.information.js';
export default class editComment extends comment{
layers = 1;
display = "flex";
debug = true;
flexFlow = "column";
gridTemplate = " '_information ' " +
" 'body ' " +
" 'body ' " +
" 'saveButton ' ";
_information = new information();
body = new commentEditBody();
title = new commentEditTitle();
saveButton = new saveButton();
width = "-webkit-fill-available";
#ifdef ANDROID
width = "100vw"
borderRadius = 18
margin = 4
#ifdef LIGHT
background = "white";
#endif
#endif
create() {
this.title.hide();
this.author.disable = true;
if( !this.id ) {
this.body.useCustomElement = true;
}
}
enableWRITE() {
this._information._editButton.show();
}
disableWRITE() {
this._information._editButton.hide();
this.body.useCustomElement = false;
this.saveButton.hide();
}
enableDELETE() {
this._information._deleteButton.show();
}
disableDELETE() {
this._information._deleteButton.hide();
}
permission() {
this.allow( this.author, "WRITE" );
this.allow( this.author, "DELETE" );
}
}

View File

@@ -0,0 +1,31 @@
import commentTitle from '../comment.title.js';
export default class commentEditTitle extends commentTitle{
useCustomElement = false;
borderLeft = "solid 1px #faebd7";
borderRight = "solid 1px #faebd7";
enableInput() {
this.background = "#373b44";
this.useCustomElement = true;
}
disableInput() {
this.background = "white";
this.useCustomElement = false;
}
}

View File

@@ -0,0 +1,35 @@
import button from '/elements/button.js';
export default class editButton extends button{
label = "Edit";
#ifdef ANDROID
fontSize = 12;
width = "auto"
height = "auto"
#endif
async click() {
this.parent.parent.body.activateTextarea();
this.parent.parent.saveButton.show();
this.hide();
}
}

View File

@@ -0,0 +1,70 @@
import userLabel from './comment.userLabel.js';
import deleteButton from './comment.deleteButton.js';
import editButton from './comment.editButton.js';
import icon from '/elements/icon.js';
class chatIcon extends icon{
margin = 12;
}
export default class information{
width = "100%";
display = "grid";
display = "flex";
flexFlow = "row";
layers = 2;
borderBottom = "#2b2c2d57";
borderTop = "#2b2c2d57";
gridTemplate = " '_deleteButton _editButton' " +
" 'userLabel userLabel' ";
gridTemplateColumns = "40px 100px";
gridTemplateRows = "40px 60px";
#ifdef WINDOWS
#ifdef LIGHT
background = "#4b94d31f";
#endif
#ifdef DARK
//background = "rgb(48 51 56 / 86%)";
#endif
#endif
_deleteButton = new deleteButton( );
_editButton = new editButton( );
_userLabel = new userLabel();
//_icon = new chatIcon("ios-chatbubbles-outline.svg", true);
}

View File

@@ -0,0 +1,36 @@
import button from '/elements/button.js';
import tools from '/unify/tools.js';
export default class saveEditButton extends button {
text = "Save Message";
display = "none";
userContract;
async click( event ){
var result = await this.socketManager.get( "table", "save", this, "sign" );
this.parent.id = false;
this.hide();
this.parent._information._editButton.show();
this.parent.body.useCustomElement = false;
//this.parent.background = "#cdf0ce";
}
}

View File

@@ -0,0 +1,34 @@
import input from '/elements/input.js';
import label from '/elements/label.js';
export default class userLabel extends label {
float = "left";
fontWeight = "bold";
padding = "12px";
paddingLeft = 26;
setAuthor( author ) {
if( author.username ) {
this.text = author.username.value;
}
}
create() {
var author = this.parent.parent.author;
this.setAuthor( author );
}
}

View File

@@ -0,0 +1,19 @@
import newsBody from '../news.body.js';
import textarea from '/elements/textarea.js';
export default class newsPageBody extends newsBody, textarea{
placeholder = "Message";
height = 120;
async keyup( event ) {
this.value = event.target.value;
}
}

View File

@@ -0,0 +1,39 @@
import button from '/elements/button.js';
export default class editButton extends button {
label = "Save";
async click( event, object ){
var result = await this.parent.parent.save();
// reset id so you can create a new row again
var editNewsDialog = this.parent.parent;
editNewsDialog.id = false;
// reset title
editNewsDialog.newsTitleRow.title.value = "";
// reset body
editNewsDialog.newsBodyRow.body.value = "";
editNewsDialog.hide();
this.parents("newsPages").newsPage.sync();
if( this.parents("newsItemPage").newsListTable ) {
this.parents("newsItemPage").newsListTable.body.update();
}
}
}

View File

@@ -0,0 +1,246 @@
import news from '../news.js';
import newsEditTitle from './news.edit.title.js';
import newsEditBody from './news.edit.body.js';
import newsEditbutton from './news.edit.button.js';
import newsEditPrice from './news.edit.price.js';
import groups from '/user/group/user.group.permission.js';
import label from '/elements/label/left.js';
import panel from '/elements/panel.js';
import frostedGlass from '/elements/window/frostedGlass.js';
import draggable from '/elements/window/draggable.js';
import button from '/elements/button.js';
import panelRow from '/elements/panel/row.js';
class newsBodyRow extends panelRow{
#ifdef WINDOWS
background = "none";
#endif
border = "none"
label = new label("Message");
body = new newsEditBody();
}
class newsTitleRow extends panelRow{
#ifdef WINDOWS
background = "none";
#endif
border = "none"
label = new label("Title");
title = new newsEditTitle();
}
class newsPriceRow extends panelRow{
#ifdef WINDOWS
background = "none";
#endif
border = "none"
label = new label("Price");
price = new newsEditPrice();
}
class cancelButton extends button{
text = "Cancel";
boxWidth = "100%"
click() {
this.parent.parent.hide();
}
}
class newsButtonRow extends panelRow{
border = "none"
cancelButton = new cancelButton();
newsEditbutton = new newsEditbutton();
#ifdef WINDOWS
background = "none";
#endif
}
import header from '/elements/window/header.js';
export default class newsEdit extends news, panel, draggable {
header = new header("News");
layers = 2;
zIndex = 10000;
#ifdef WINDOWS
fontFamily = "segoe";
#endif
#ifdef MACOS
fontFamily = "sf-ui";
width = 600;
#ifdef DARK
background = "#161110bf";
#endif
#ifdef LIGHT
//background = "white";
background = "#fdfdfdbf"
#endif
backdropFilter = "blur(22px)";
#endif
#ifdef WINDOWS
fontFamily = "SegoeUI";
width = 600;
#ifdef DARK
background = "#202020cc";
border = "1px solid rgb(44 45 46)";
#endif
#ifdef LIGHT
//background = "white";
background = "#fdfdfdbf"
#endif
backdropFilter = "blur(22px)";
#endif
selector = "#application";
display = "none";
flexDirection = "column";
debug = true;
position = "absolute"
boxBackgroundImage;
newsTitleRow = new newsTitleRow();
newsPriceRow = new newsPriceRow();
newsBodyRow = new newsBodyRow();
newsButtonRow = new newsButtonRow();
debug = true;
height = "fit-content";
async create() {
//await this.sync();
//this.hide();
}
afterLoad() {
this.center();
}
permission() {
this.allow( groups.member, "READ" );
this.allow( groups.admin, "READ" );
this.allow( groups.visitor, "READ" );
this.allow( groups.member, "WRITE" );
this.allow( groups.admin, "WRITE" );
this.allow( groups.visitor, "WRITE" );
}
}

View File

@@ -0,0 +1,18 @@
import newsPrice from '../news.price.js';
import input from '/elements/input.js';
export default class newsPagePrice extends newsPrice, input{
placeholder = "Price";
async keyup( event ) {
this.value = event.target.value;
}
}

View File

@@ -0,0 +1,11 @@
import newsTitle from '../news.title.js';
import input from '/elements/input.js';
export default class newsEditTitle extends input, newsTitle{
placeholder = "Title";
}

View File

@@ -0,0 +1,140 @@
import input from "/elements/input.js"
import page from "/elements/page.js"
class label{
constructor( text ) {
this.text = text;
}
background = "#0000002e"
borderRadius = 6;
margin = 10;
padding = 26;
}
class a extends input{
boxAlignItems = "center"
boxJustifyContent = "center";
boxColor = "black"
}
class b extends input{
//background = "blue";
boxAlignItems = "center"
boxJustifyContent = "center";
boxColor = "black"
}
class c extends input{
//background = "yellow";
boxAlignItems = "center"
boxJustifyContent = "center";
boxColor = "black"
}
class d extends input{
//boxBackground = "grey";
boxAlignItems = "center"
boxJustifyContent = "center";
boxColor = "black"
}
class gridA{
display = "grid";
gridTemplate = `
"label label"
"a a"
"b d"
"c d"
`;
height = 400;
width = "100%"
label = new label("This is the first Grid, Press tab to navigate trough the inputs.");
a = new a();
b = new b();
c = new c();
d = new d();
}
class gridB{
display = "grid";
gridTemplate = `
"label label"
"d d"
"a empty"
"b b"
`;
height = 400;
width = "100%"
label = new label("This is the second Grid, Press tab to navigate trough the inputs.");
a = new a();
b = new b();
c = new c();
d = new d();
}
export default class gridExample extends page{
flexDirection = "column"
gridA = new gridA();
gridB = new gridB();
}

View File

@@ -0,0 +1,102 @@
import icon from "/elements/icon.js";
#ifdef SERVER
import fs from "fs";
#endif
const delay = time => new Promise(res=>setTimeout(res,time));
export default class deleteFileIconButton extends icon{
width = 24;
height = 24;
propegateEvent = false;
boxMarginTop = "-12px";
boxBorderRadius = 14;
boxBackground = "#ffffffbf";
boxWidth = "fit-content";
boxPadding = 2;
boxPosition = "absolute";
boxMarginLeft = -8;
boxDisplay = "none";
async click() {
this.parent.opacity = "0%";
await delay(200)
this.parent.background = "none";
this.parent.width = 0;
this.parent.margin = 0;
this.parent.padding = 0;
this.parent.border = "none"
await delay(200)
this.parent.hide();
this.parent.remove();
var fileName = this.parent.value;
await this.removeFile( fileName );
}
node async removeFile( fileName ) {
var absolutePath = path.resolve( "./assets/uploads/" + fileName );
console.log("Removing file test", absolutePath);
if( fs.existsSync( absolutePath ) ) {
fs.unlinkSync( absolutePath );
console.log("File is removed.");
} else {
console.log("File does not exist.");
}
}
constructor() {
super("close.svg")
}
create() {
this.hide();
}
}

View File

@@ -0,0 +1,164 @@
import icon from "/elements/icon.js";
import deleteButton from "./fileManager.icon.deleteButton.js";
export default class fileIcon extends icon{
boxSizing = "border-box"
border = "none"
opacity = "100%"
fontSize = "0"
propegateEvent = false;
backgroundSize = "cover!important"
width = 60;
height = 60;
borderRadius = 12;
margin = 6;
display = "block";
float = "left";
layers = 1;
border = "2px solid #F7FAFC"
cursor = "pointer";
deleteButton = new deleteButton();
mode = "show";
//transition = "2s"
toggleEditMode() {
if( this.mode == "show" ) {
this.deleteButton.show();
this.mode = "edit";
this.rotateAnimation.play();
} else {
this.deleteButton.hide();
this.mode = "show";
this.rotateAnimation.stop();
}
}
create() {
this.setImage( "'/assets/uploads/" + this.value + "'" );
this.createKeyFrame();
this.opacityAnimation.play();
}
createKeyFrame() {
this.rotateAnimation = this.createAnimation("rotateAnimation");
var randomTime = "0."+ 2 + Math.floor(Math.random() *1000);
this.rotateAnimation.setDuration( randomTime +"s" );
this.rotateAnimation.setIterationCount("infinite");
var key = this.rotateAnimation.createKeyFrame( 0 );
key.setProperty( "rotate", "3deg" );
var key = this.rotateAnimation.createKeyFrame( 50 );
key.setProperty( "rotate", "-3deg" );
var key = this.rotateAnimation.createKeyFrame( 100 );
key.setProperty( "rotate", "3deg" );
this.opacityAnimation = this.createAnimation("opacityAnimation");
this.opacityAnimation.setIterationCount("1");
this.opacityAnimation.setDuration( "0.9s" );
this.opacityAnimation.setFillMode( "forwards" );
var key = this.opacityAnimation.createKeyFrame( 0 );
key.setProperty( "opacity", "0" );
key.setProperty( "display", "none" );
var key = this.opacityAnimation.createKeyFrame( 1 );
key.setProperty( "opacity", "0" );
key.setProperty( "display", "block" );
var key = this.opacityAnimation.createKeyFrame( 100 );
key.setProperty( "display", "block" );
key.setProperty( "opacity", "100%" );
}
mouseover() {
this.border = "2px solid rgb(125 177 211)";
}
mouseleave() {
this.border = "2px solid #F7FAFC";
//this.rotateAnimation.stop();
}
async click() {
var previewWindow = this.parent.parent.previewWindow;
previewWindow.setTitle( this.value );
previewWindow.show("block");
previewWindow.center();
previewWindow.setImage( "/assets/uploads/" + this.value );
}
}

View File

@@ -0,0 +1,70 @@
import draggable from '/elements/window/draggable.js';
import page from '/elements/page.js';
import windowHeader from '/elements/window/header.js';
import previewImage from './preview/previewWindow.image.js';
export default class imagePreviewWindow extends draggable{
selector = "#application";
backdropFilter = "blur(22px)";
paddingBottom = 30;
display = "none"
#ifdef WINDOWS
#ifdef LIGHT
background = "rgb(255 255 255 / 75%)"
#endif
#ifdef DARK
background = "#202020cc"
#endif
#endif
create() {
this.center();
this.hide();
}
width = 600;
flexDirection = "column";
borderRadius = 12;
windowHeader = new windowHeader();
previewImage = new previewImage();
setTitle( title ) {
this.windowHeader.setTitle( title );
}
setImage( path ) {
this.previewImage.setImage( path )
}
}

View File

@@ -0,0 +1,44 @@
import fileupload from './fileManager.upload.js';
import fileList from "./fileManager.list.js";
import removeIcons from "./fileManager.removeIcons.js";
import header from '/elements/header.js';
import page from '/elements/page.js';
import previewWindow from "./fileManager.imagePreviewWindow.js"
import fileChooser from '/elements/fileChooser/fileChooser.js';
export default class fileManager extends page{
width = "100%"
minHeight = 350;
flexDirection = "column"
uploadHeader = new header("Upload");
fileupload = new fileupload();
filesHeader = new header("Files");
removeIcons = new removeIcons();
fileList = new fileList();
previewWindow = new previewWindow();
fileChooser = new fileChooser();
}

View File

@@ -0,0 +1,55 @@
import fileIcon from "./fileManager.icon.js";
import panel from "/elements/panel/row.js";
#ifdef SERVER
import fs from "fs";
import path from "path";
#endif
export default class fileList extends panel{
margin = 20;
padding = 20;
display = "block";
async create() {
this.empty();
var files = await this.readFiles();
}
node async readFiles() {
var absolutePath = path.resolve( "./assets/uploads/" );
var files = fs.readdirSync( absolutePath );
for (var i = 0; i < files.length; i++) {
var file = files[i];
var currentFileIcon = new fileIcon();
currentFileIcon.value = file;
this.add( currentFileIcon );
}
return files;
}
}

View File

@@ -0,0 +1,74 @@
import icon from "/elements/icon.js";
export default class removeIcons extends icon{
width = 14;
height = 14;
margin = 4;
propegateEvent = false;
backgroundSize = "contain!important"
cursor = "pointer";
boxMarginTop = "17px";
boxBorderRadius = 14;
boxBackground = "#ffffffbf";
boxWidth = "fit-content";
boxPadding = 2;
//boxPosition = "";
boxMarginLeft = 11;
boxMarginBottom = -37;
constructor() {
super("edit.svg");
}
mode = "normal";
click() {
var icons = this.parent.fileList.getChildren();
for (var i = 0; i < icons.length; i++) {
var icon = icons[i];
icon.toggleEditMode();
}
if(this.mode == "normal") {
this.setImage("/assets/images/icons/stop.png")
this.mode = "wiggle";
} else {
this.mode = "normal";
this.setImage("/assets/images/icons/edit.svg")
}
}
}

View File

@@ -0,0 +1,150 @@
import fileUpload from "/elements/fileUpload.js";
import fileIcon from "./fileManager.icon.js";
#ifdef SERVER
import fs from "fs";
import path from "path";
//import android from 'androidjs';
#endif
export default class stream extends fileUpload {
placeholder = "Upload."
margin = 20;
stream;
type;
/*
inputType = "button";
click( event ) {
//this.android_file_chooser();
//var fileChooser = this.parent.fileChooser;
//fileChooser.show("flex")
//fileChooser.open();
}
*/
async change( event ) {
var input = this.customElement;
var files = input.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
var chunksize = 64 * 1024;
var offset = 0;
var filename = file.name.replaceAll(" ", "_");
await this.createStream( filename );
while( offset < file.size ) {
const chunkfile = await file.slice( offset, offset + chunksize );
const chunk = await chunkfile.arrayBuffer();
var intChunk = new Int8Array( chunk );
this.writeChunk( intChunk )
offset += chunksize;
}
await this.endstream();
}
}
node async createStream( filename ) {
var absolutePath = path.resolve( "./assets/uploads/" + filename );
this.filename = filename;
console.log("Writing file to path", absolutePath);
this.stream = fs.createWriteStream( absolutePath, { encoding: 'binary' } );
this.stream.on('finish', function() {
console.log('file has been written');
});
}
node async writeChunk( chunk ) {
this.stream.write( Buffer.from( Object.values( chunk ) ) );
}
node async endstream() {
this.stream.end();
var currentFileIcon = new fileIcon();
currentFileIcon.value = this.filename;
this.parent.fileList.add( currentFileIcon );
}
node async android_file_chooser() {
//const status = app.mobiledata.isEnabled();
//console.log(android);
//console.log("Mobile Data status: ", android);
//console.log(app.mobiledata.isEnabled());
}
//mouseover() {
// console.log("mouseover??", this.parent.removeIcons)
// if( this.parent.removeIcons.mode == "wiggle" ) {
// this.parent.removeIcons.click();
// }
//}
}

View File

@@ -0,0 +1,24 @@
import image from "/elements/image.js";
export default class previewImage extends image{
layers = 1;
width = "90%"
//height = "100%"
margin = "0 auto";
backgroundSize = "contain!important";
propegateEvent = false;
borderRadius = 12;
transition = "1s"
maxHeight = "87vh"
}

View File

@@ -0,0 +1,51 @@
import menuButton from './leftSide.button.js';
export default class settingsButton extends menuButton{
text = "Animations";
create() {
//this.activateButton();
}
touchstart() {
this.click();
}
async click() {
this.stateMachine.composeState( "Animations" );
this.openPage();
}
state openPage() {
var application = this.parent.parent;
var rightSide = application.rightSide;
var menu = application.leftSide;
this.deactivateButtons();
this.activateButton();
rightSide.hideChildren();
rightSide.animations.show();
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
}

View File

@@ -0,0 +1,51 @@
import menuButton from './leftSide.button.js';
export default class fileMangerButton extends menuButton{
text = "File Manager";
create() {
//this.activateButton();
}
touchstart() {
this.click();
}
async click() {
this.stateMachine.composeState( "File Manager" );
this.openPage();
}
state openPage() {
var application = this.parent.parent;
var rightSide = application.rightSide;
this.deactivateButtons();
this.activateButton();
rightSide.hideChildren();
rightSide.fileManager.show();
rightSide.fileManager.fileList.create();
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
}

View File

@@ -0,0 +1,51 @@
import menuButton from './leftSide.button.js';
export default class settingsButton extends menuButton{
text = "Grids";
create() {
//this.activateButton();
}
touchstart() {
this.click();
}
async click() {
this.stateMachine.composeState( "Appearance" );
this.openPage();
}
state openPage() {
var application = this.parent.parent;
var rightSide = application.rightSide;
var menu = application.leftSide;
this.deactivateButtons();
this.activateButton();
rightSide.hideChildren();
rightSide.gridExample.show();
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
}

View File

@@ -0,0 +1,202 @@
export default class menuButton{
width = 110;
color;
background;
fontWeight;
#ifdef ANDROID
width = "80vw";
borderRadius = 18;
textAlign = "center"
padding = 20;
margin = "0 auto"
marginTop = 8;
fontSize = 20;
height = 24;
fontWeight = "600"
fontColor = "#313131";
#ifdef LIGHT
borderBottom = "1px solid #ececec";
background = "white";
#endif
#endif
#ifdef WINDOWS
borderRadius = 6;
width = 160
padding = 10;
marginTop = 2;
marginBottom = 2;
paddingLeft = 30;
#endif
activated = false;
propegateEvent = false;
cursor = "pointer";
#ifdef WINDOWS
#ifdef DARK
hightlightBackgroundColor = "#2d2d2d";
hightlightBackgroundColor = "#0c0e165c";
#endif
#ifdef LIGHT
hightlightBackgroundColor = "rgb(141 180 189 / 12%)";
#endif
#endif
#ifdef MACOS
borderRadius = 8;
padding = 10;
paddingLeft = 20;
fontSize = 16;
fontWeight = "600";
hightlightBackgroundColor = "rgb(189 193 221 / 22%)"
#ifdef LIGHT
color = "#343434";
#endif
#endif
activated = false;
state activateButton() {
this.activated = true;
this.highlightButton();
}
state deactivateButton() {
this.activated = false;
this.lowlightButton();
}
highlightButton() {
this.background = this.hightlightBackgroundColor;
if( !this.activated ) {
}
}
lowlightButton() {
if( !this.activated ) {
this.background = "";
}
}
mouseover() {
this.highlightButton();
}
mouseleave() {
if( !this.activated ) {
this.lowLightButtons();
}
}
deactivateButtons() {
var children = this.parent.getChildren();
for (var i = 0; i < children.length; i++) {
var child = children[i];
if( child.deactivateButton ) {
child.deactivateButton();
}
}
}
lowLightButtons() {
var children = this.parent.getChildren();
for (var i = 0; i < children.length; i++) {
var child = children[i];
if( child.lowlightButton ){
child.lowlightButton();
}
}
}
}

View File

@@ -0,0 +1,74 @@
import menuButton from './leftSide.button.js';
export default class newsButton extends menuButton{
text = "Home";
#ifdef MACOS
borderTopLeftRadius = 8;
#endif
create() {
var pathName = window.location.pathname;
var pathParts = pathName.split("/");
if( !pathParts[1] ) {
this.stateMachine.composeState( );
}
//this.activateButton();
this.openPage();
}
async click() {
this.stateMachine.composeState( "Home" );
this.openPage();
}
state openPage() {
var application = this.parent.parent;
var rightSide = application.rightSide;
#ifdef ANDROID
application.minimizeButton.close();
#endif
this.deactivateButtons();
//this.activateButton();
console.log("rightSide", rightSide);
rightSide.newsPages.newsItemPage.transform = "translateX(0)";
rightSide.newsPages.newsPage.transform = "translateX(0)";
rightSide.hideChildren();
rightSide.newsPages.show();
//rightSide.newsPages.newsItemPage.newsListTable.body.sync()
}
}

View File

@@ -0,0 +1,51 @@
import menuButton from './leftSide.button.js';
export default class settingsButton extends menuButton{
text = "Appearance";
create() {
//this.activateButton();
}
touchstart() {
this.click();
}
async click() {
this.stateMachine.composeState( "Appearance" );
this.openPage();
}
state openPage() {
var application = this.parent.parent;
var rightSide = application.rightSide;
var menu = application.leftSide;
this.deactivateButtons();
this.activateButton();
rightSide.hideChildren();
rightSide.settings.show();
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
}

View File

@@ -0,0 +1,62 @@
import menuButton from './leftSide.button.js';
import groups from '/user/group/user.group.permission.js';
export default class signinPageButton extends menuButton{
text = "Signin";
click() {
this.stateMachine.composeState( "Signin" );
this.openPage();
}
state openPage() {
this.deactivateButtons();
this.activateButton();
var application = this.parent.parent;
var rightSide = application.rightSide;
rightSide.width = "";
rightSide.hideChildren();
rightSide.signin.show();
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
enableREAD() {
this.hide();
}
disableREAD() {
this.show();
}
permission() {
this.allow( groups.member , "READ" );
this.allow( groups.admin , "READ" );
}
}

View File

@@ -0,0 +1,94 @@
import menuButton from './leftSide.button.js';
import groups from '/user/group/user.group.permission.js';
import userManager from '/server/userManager.js';
export default class signoutButton extends menuButton{
text = "Signout";
create(){
this.hide();
}
async click() {
this.stateMachine.composeState( "Signout" );
await this.openPage();
}
state async openPage() {
this.deactivateButtons();
this.activateButton();
console.log("before process",this);
var visitorUser = await this.signout();
localStorage.setItem( "username", false );
localStorage.setItem( "sessionKey", false );
this.getCore().updatePermissions( visitorUser.permissionObjects );
}
createVisitor( client ) {
var userInstance = new user();
userInstance.username.value = "Visitor";
userInstance.id = 0;
userInstance.permissionObjects = userManager.getPermissions( userInstance, client );
return userInstance;
}
node async signout( object, client ) {
var newUser = this.createVisitor( this.client );
this.client.user = newUser;
global.core.setUserObjects( false, this.client );
return newUser;
}
enableREAD() {
this.show();
}
disableREAD() {
this.hide();
}
permission() {
this.allow( groups.member , "PROCESS" );
this.allow( groups.admin , "PROCESS" );
this.allow( groups.member , "READ" );
this.allow( groups.admin , "READ" );
}
}

View File

@@ -0,0 +1,37 @@
import menuButton from './leftSide.button.js';
export default class signinButton extends menuButton{
text = "Signup";
click() {
this.stateMachine.composeState( "Signup" );
this.openPage();
}
state openPage() {
this.deactivateButtons();
this.activateButton();
var application = this.parent.parent;
var rightSide = application.rightSide;
rightSide.hideChildren();
rightSide.signup.show();
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
}

View File

@@ -0,0 +1,68 @@
import menuButton from './leftSide.button.js';
import groups from '/user/group/user.group.permission.js';
export default class usersPageButton extends menuButton{
text = "Users";
click() {
this.stateMachine.composeState( "Users" );
this.openPage();
}
state openPage() {
this.deactivateButtons();
this.activateButton();
var application = this.parent.parent;
var rightSide = application.rightSide;
rightSide.width = "";
rightSide.hideChildren();
rightSide.userListPage.show();
rightSide.userListPage.userTable.body.create()
#ifdef ANDROID
application.minimizeButton.close();
#endif
}
enableREAD() {
this.show();
}
disableREAD() {
this.hide();
}
permission() {
this.allow( groups.member , "PROCESS" );
this.allow( groups.admin , "PROCESS" );
this.allow( groups.member , "READ" );
this.allow( groups.admin , "READ" );
}
}

View File

@@ -0,0 +1,33 @@
import header from "/elements/header.js";
export default class menuHeader extends header{
text = "Menu";
flexDirection = "column";
fontSize = 36;
paddingTop = 100;
fontWeight = "300"
paddingBottom = 100;
textAlign = "center";
width = "100vw";
#ifdef ANDROID
display = "flex";
#else
display = "none";
#endif
}

View File

@@ -0,0 +1,179 @@
import button from '/elements/button.js';
import newsButton from './leftSide.button.news.js';
import settingsButton from './leftSide.button.settings.js';
import signinButton from './leftSide.button.signin.js';
import signupButton from './leftSide.button.signup.js';
import signoutButton from './leftSide.button.signout.js';
import fileManagerButton from './leftSide.button.fileManager.js';
import animationsButton from './leftSide.button.animations.js';
import usersButton from './leftSide.button.users.js';
import gridButton from './leftSide.button.grid.js';
import menuHeader from './leftSide.header.js';
export default class leftSide{
state = "visible";
boxOverflow = "hidden";
boxTransition = "0.3S";
#ifdef ANDROID
boxHeight = "100vh";
#else
boxHeight = "";
#endif
boxWidth = 220;
width = 220;
flexDirection = "column";
//borderRight = "1px solid #3D3D3D"
paddingTop = 30;
//minHeight = "40vh"
header = new menuHeader();
#ifdef ANDROID
height = "100vh";
paddingTop = "";
boxWidth = "100vw";
width = "100vw";
#ifdef LIGHT
boxBackground = "#f2f2f2";
//background = "white";
#endif
#endif
#ifdef WINDOWS
paddingLeft = 4;
paddingRight = 4;
#ifdef DARK
background = "#202020cc";
color = "white"
borderRight = "1px solid black"
fontWeight = "500";
#endif
#ifdef LIGHT
background = "rgb(255 255 255 / 75%)";
color = "black";
fontWeight = "200";
#endif
#endif
#ifdef MACOS
paddingTop = 40;
paddingLeft = 20;
#ifdef DARK
background = "rgb(40 22 22 / 75%)";
color = "white"
borderRight = "1px solid black"
fontWeight = "bold";
#endif
#ifdef LIGHT
background = "rgb(255 255 255 / 75%)";
color = "black";
fontWeight = "200";
#endif
#endif
render() {
}
//opacity = "90%";
//backdropFilter = "blur(20px)";
borderTopLeftRadius = 8;
borderBottomLeftRadius = 8;
borderTopLeftRadius = "12px";
borderBottomLeftRadius = "12px";
newsButton = new newsButton();
settingsButton = new settingsButton();
usersButton = new usersButton();
signinButton = new signinButton();
signoutButton = new signoutButton();
signupButton = new signupButton();
animationsButton = new animationsButton();
gridButton = new gridButton();
fileManagerButton = new fileManagerButton();
}

View File

@@ -0,0 +1,21 @@
export default class testChild{
text = "test";
width = 160;
height = 30;
background = "red";
fontSize = 24;
constructor( abc ) {
this.text = abc;
}
}

View File

@@ -0,0 +1,29 @@
import groups from '/user/group/user.group.permission.js';
import gridViewColumn from '/elements/gridView/gridView.header.row.column.js';
export default class newsListHeaderActions extends gridViewColumn {
text = "Actions";
enableDELETE() {
this.show();
}
disableDELETE() {
this.hide();
}
permission() {
this.allow( groups.admin, "DELETE" );
}
}

View File

@@ -0,0 +1,9 @@
import gridViewColumn from '/elements/gridView/gridView.header.row.column.js';
export default class newsListHeaderBody extends gridViewColumn{
text = "Message";
}

View File

@@ -0,0 +1,26 @@
import news from '../../news.js';
import body from './news.list.header.body.js';
import title from './news.list.header.title.js';
import price from './news.list.header.price.js';
import actions from './news.list.header.actions.js';
import gridViewRow from '/elements/gridView/gridView.header.row.js';
export default class newsListHeader extends news, gridViewRow {
body = new body();
title = new title();
price = new price();
actions = new actions();
}

View File

@@ -0,0 +1,10 @@
import gridViewColumn from '/elements/gridView/gridView.header.row.column.js';
export default class newsListHeaderPrice extends gridViewColumn{
text = "Price";
}

View File

@@ -0,0 +1,12 @@
import newsTitle from '../../news.title.js';
import gridViewColumn from '/elements/gridView/gridView.header.row.column.js';
export default class newsListHeaderTitle extends gridViewColumn{
text = "Title";
}

View File

@@ -0,0 +1,32 @@
import button from "/elements/button.js";
import groups from '/user/group/user.group.permission.js';
export default class deleteButton extends button {
text = "Delete";
propegateEvent = false;
async click() {
var sure = confirm("Are you sure you want to delete this item");
if( sure ) {
await this.parent.parent.delete();
this.parent.parent.remove();
}
}
permission() {
this.allow( groups.admin, "DELETE" );
}
}

View File

@@ -0,0 +1,23 @@
import gridViewColumn from '/elements/gridView/gridView.body.row.column.js';
import deleteButton from "./news.list.item.actions.deleteButton.js"
export default class newsListItemActions extends gridViewColumn, gridViewColumn{
useCustomElement = false;
padding = 20;
display = "table-cell";
layers = 1;
paddingLeft = 30;
borderRadius;
deleteButton = new deleteButton();
}

View File

@@ -0,0 +1,9 @@
import newsBody from '../../news.body.js';
import gridViewColumn from '/elements/gridView/gridView.body.row.column.js';
export default class newsListItemBody extends newsBody, gridViewColumn{
}

View File

@@ -0,0 +1,123 @@
import news from '../../news.js';
import body from './news.list.item.body.js';
import title from './news.list.item.title.js';
import price from './news.list.item.price.js';
import actions from './news.list.item.actions.js';
import groups from '/user/group/user.group.permission.js';
import gridViewRow from '/elements/gridView/gridView.body.row.js';
export default class newsListItem extends news, gridViewRow {
body = new body();
title = new title();
price = new price();
actions = new actions();
cursor = "pointer";
background;
#ifdef MACOS
fontSize = 14;
#endif
hoverBackgroundColor = "#363333"
#ifdef DARK
//mouseHoverColor = "#363333";
#endif
#ifdef LIGHT
//mouseHoverColor = "rgb(255 255 255 / 95%)";
#endif
async click() {
this.stateMachine.composeState( this.id, this.value );
await this.loadPage( this.id );
}
state async loadPage( id ) {
var rightSide = this.parents("newsPages");
var boundBox = this.defaultElement.getBoundingClientRect();
var width = boundBox.width;
#ifdef ANDROID
rightSide.newsItemPage.translateX = -width;
#elif
rightSide.newsItemPage.transform = "translateX(-600px)";
rightSide.newsPage.transform = "translateX(-600px)";
#endif
var newsPage = rightSide.newsPage;
newsPage.id = this.id;
await newsPage.sync();
newsPage.createComment.create();
//newsPage.updateFrostglass();
//newsPage.show();
}
mouseover() {
//this.background = this.mouseHoverColor;
}
mouseleave() {
//this.background = "none";
}
enableDELETE() {
this.actions.show();
}
disableDELETE() {
this.actions.hide();
}
permission() {
this.allow( groups.admin, "DELETE" );
}
}

View File

@@ -0,0 +1,20 @@
import newsPrice from '../../news.price.js';
import gridViewColumn from '/elements/gridView/gridView.body.row.column.js';
export default class newsListItemPrice extends newsPrice, gridViewColumn{
create() {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR',
});
this.text = formatter.format( this.value );
}
}

View File

@@ -0,0 +1,10 @@
import newsTitle from '../../news.title.js';
import gridViewColumn from '/elements/gridView/gridView.body.row.column.js';
export default class newsListItemTitle extends newsTitle, gridViewColumn{
}

View File

@@ -0,0 +1,168 @@
import renderCollection from '/unify/renderCollection.js';
import groups from '/user/group/user.group.permission.js';
import gridViewBody from '/elements/gridView/gridView.body.js';
import OR from '/unify/sql/OR.js';
import AND from '/unify/sql/AND.js';
import LIKE from '/unify/sql/LIKE.js';
import GREATER from '/unify/sql/GREATER_OR_EQUAL.js';
import SMALLER from '/unify/sql/SMALLER_OR_EQUAL.js';
export default class newsListTableBody extends renderCollection, gridViewBody {
debug = true;
sort = "title";
page = 0;
limit = 2;
async create() {
//this.update();
}
async update( updatePagination = true ) {
if( updatePagination ) {
this.page = 0;
}
this.numberOfPages = await this.filterSearch( this.searchType, this.searchTerm, this.sort, this.direction, this.limit, this.page );
await this.sync();
if( updatePagination ) {
this.parents("newsItemPage").tableControl.pagination.create();
}
}
node async filterSearch( searchType, searchTerm, order, direction, limit, page ) {
console.log("searchType", searchType);
console.log("search input", searchTerm);
console.log("search order", order);
console.log("direction", direction);
console.log("limit", limit);
console.log("from", page * limit);
var filter = this.getFilter();
switch( searchType ) {
case 0:
filter.search = OR( OR( LIKE( filter.title, searchTerm ), LIKE( filter.comments.body, searchTerm ) ), LIKE( filter.body, searchTerm ) );
break;
case 1:
filter.search = GREATER( filter.price, searchTerm );
break;
case 2:
filter.search = SMALLER( filter.price, searchTerm );
break;
}
if( !searchTerm ) {
filter.search = false;
}
switch( order ) {
case "title":
filter.order = filter.title;
break;
case "body":
filter.order = filter.body;
break;
case "price":
filter.order = filter.price;
break;
}
if( direction == "desc" ) {
filter.direction = "desc";
} else {
filter.direction = "asc";
}
filter.limit = 1000;
filter.from = 0;
// See how many searched rows there are in total
this.get();
filter.limit = parseInt( limit );
filter.from = parseInt( page * limit );
var numberOfPages = Math.ceil( this.rows.length / limit );
console.log("numberOfPages", numberOfPages);
console.log("this.rows.length", this.rows.length);
console.log("limit", limit);
return numberOfPages;
}
permission() {
this.allow( groups.visitor, "READ" );
this.allow( groups.member, "READ" );
this.allow( groups.admin, "READ" );
}
}

View File

@@ -0,0 +1,13 @@
import newsListHeaderRow from "./header/news.list.header.js";
import document from '/unify/document.js';
import gridViewHeader from '/elements/gridView/gridView.header.js';
export default class newsListTableHeader extends gridViewHeader {
newsListHeaderRow = new newsListHeaderRow();
}

View File

@@ -0,0 +1,34 @@
import newsListTableHeader from "./news.list.table.header.js";
import newsListTableBody from "./news.list.table.body.js";
import collection from '/unify/collection.js';
import newsListItem from '../list/item/news.list.item.js';
import news from '../news.js';
import gridView from '/elements/gridView/gridView.js';
export default class newsListTable extends gridView{
header = new newsListTableHeader();
body = new newsListTableBody( newsListItem, new collection( news ) );
#ifdef MACOS
marginLeft = 6;
width = "auto"
margin = "6px -2px 10px 6px"
#endif
}

View File

@@ -0,0 +1,122 @@
import icon from '/elements/icon.js';
export default class minimizeButton {
//showFps = true;
text = "<"
color = "white"
fontWeight = "bold"
cursor = "pointer"
propegateEvent = false
zIndex = 5000
transition = "1s easeInOutQuart";
margin = 10;
position = "absolute";
#ifdef ANDROID
top = "4px"
#else
bottom = "4px"
#endif
left = "0"
zIndex = "100000"
transform = "rotate(0)" ;
open() {
var menu = this.parent.leftSide;
//this.setImage("chevron-right.svg")
this.transform = "scale(1, 1)";
#ifdef ANDROID
menu.boxHeight = "100vh";
#else
menu.boxWidth = 220;
#endif
menu.state = "visible";
this.parent.rightSide.borderRadius = "";
}
close() {
var menu = this.parent.leftSide;
//this.setImage("chevron-left.svg")
this.transform = "scale(-1, 1)";
#ifdef ANDROID
menu.boxHeight = "0";
#else
menu.boxWidth = "0";
#endif
menu.state = "hidden";
var that = this;
setTimeout(function(){
console.log("asd");
that.parent.rightSide.borderRadius = 12;
}, 1000)
}
click() {
var menu = this.parent.leftSide;
var state = menu.state;
if( state == "visible" ) {
this.close();
} else {
this.open();
}
}
}

View File

@@ -0,0 +1,7 @@
import column from '/unify/column.js';
export default class body extends column{
}

29
application/demo/news.js Normal file
View File

@@ -0,0 +1,29 @@
import table from '/unify/table.js';
import title from './news.title.js';
import body from './news.body.js';
import price from './news.price.js';
import user from '/user/user.js';
import comment from './comment/comment.js';
import collection from '/unify/collection.js';
export default class news extends table {
title = new title();
body = new body();
price = new price();
comments = new collection( comment );
}

View File

@@ -0,0 +1,11 @@
import column from '/unify/column.js';
import datatype from '/unify/datatype.js';
export default class price extends column{
datatype = datatype.REAL;
}

View File

@@ -0,0 +1,9 @@
import column from '/unify/column.js';
export default class title extends column {
}

View File

@@ -0,0 +1,77 @@
import icon from "/elements/icon.js";
export default class backButton{
text = "<"
color = "white"
fontWeight = "bold"
cursor = "pointer";
propegateEvent = false
transition = "2s";
margin = 10;
marginLeft = 20;
fontFamily = "unset"
#ifdef WINDOWS
#ifdef DARK
#endif
#ifdef LIGHT
#endif
#endif
#ifdef MACOS
#ifdef DARK
boxBackground = "#282828";
#endif
#ifdef LIGHT
boxBackground = "#ffffff";
#endif
#endif
click() {
this.stateMachine.composeState( "Home" );
this.openNewsItems();
}
state openNewsItems() {
var rightSide = this.parents("newsPages");
//rightSide.newsItemPage.marginLeft = "0";
rightSide.newsItemPage.transform = "translateX(0)";
rightSide.newsPage.transform = "translateX(0)";
}
}

View File

@@ -0,0 +1,57 @@
import newsBody from '../news.body.js';
import textarea from '/elements/textarea.js';
import flexbox from '/elements/flexbox.js';
export default class newsPageBody extends flexbox, newsBody{
padding = 20;
width = "-webkit-fill-available"
#ifdef MACOS
#ifdef DARK
background = "#282828";
#endif
#ifdef LIGHT
background = "#ffffff";
#endif
#endif
#ifdef WINDOWS
#ifdef DARK
#endif
#ifdef LIGHT
#endif
#endif
#ifdef ANDROID
borderRadius = "0 0 18px 18px"
#ifdef LIGHT
background = "white";
#endif
#endif
}

View File

@@ -0,0 +1,49 @@
import button from "/elements/button.js";
export default class backButton extends button{
text = "Edit News"
color = "white"
fontWeight = "bold"
cursor = "pointer";
float = "right"
propegateEvent = false
transition = "2s";
margin = 10;
marginLeft = 20;
fontFamily = "unset"
click() {
this.stateMachine.composeState( "Edit" );
var rightSide = this.parents("newsPages");
//this.parent.hide();
var newsEdit = rightSide.newsEdit;
newsEdit.id = this.parent.id;
newsEdit.sync();
newsEdit.show();
//rightSide.newsPage.transform = "translateX(-1200px)";
}
}

View File

@@ -0,0 +1,254 @@
import news from '../news.js';
import newsPageTitle from './news.page.title.js';
import newsPageBody from './news.page.body.js';
import groups from '/user/group/user.group.permission.js';
import commentsMessages from '../comment/comments.messages.js';
import editComment from '../comment/edit/comment.edit.js';
import createComment from '../comment/create/comment.create.js';
import saveButton from './news.page.save.js';
import backButton from "./news.page.backButton.js"
import editButton from "./news.page.edit.button.js"
import filler from "/elements/filler.js";
import searchComments from "./search.comments.js";
import tools from "/unify/tools.js";
class testDiv{
pageTitle = new newsPageTitle();
}
class testSuffixes{
layers = 1;
useCustomElement = true;
customElement = document.createElement("a")
text = "visit Unify";
visitedColor = "green";
linkColor = "#009688"
activeColor = "red"
create() {
this.element.setAttribute("href", "https://unifyjs.org")
}
}
export default class newsPage extends news{
willChange = "transform";
transform;
minHeight = "100%";
transition = "1s"
scrollbarWidth = "6px";
scrollbarTrackBackground = "#1c1d1e";
scrollbarThumbBackground = "#404040"
scrollbarThumbBorderRadius = "4px"
scrollbarThumbHoverBackground = "grey";
#ifdef MACOS
#ifdef LIGHT
background = "white";
#endif
#ifdef DARK
background = "#282828";
#endif
#endif
#ifdef WINDOWS
#ifdef LIGHT
background = "rgb(255 255 255 / 75%)";
#endif
#ifdef DARK
background = "#202020cc";
#endif
#endif
#ifdef ANDROID
height = "100vh";
paddingTop = "";
boxWidth = "100vw";
width = "100vw";
#ifdef LIGHT
background = "#f2f2f2";
#endif
#endif
flexDirection = "column"
_backButton = new backButton();
_testDiv = new testDiv();
body = new newsPageBody();
editButton = new editButton();
//testSuffixes = new testSuffixes();
debug = true;
width = 600;
sizing = "border-box";
layers = 1;
height = "70vh"
overflowY = "auto"
searchComments = new searchComments();
commentsMessages = new commentsMessages( editComment, this.comments );
createComment = new createComment( this.comments );
filler = new filler();
async afterLoad() {
var pathName = window.location.pathname;
var pathParts = pathName.split("/");
var id = parseFloat( pathParts[1] );
if( id ) {
this.stateMachine.composeState();
this.showParents();
this.show();
await this.loadPage( id )
}
}
state async loadPage( id ) {
var rightSide = this.parents("newsPages");
var boundBox = this.defaultElement.getBoundingClientRect();
var width = boundBox.width;
#ifdef ANDROID
rightSide.newsItemPage.translateX = -width;
#elif
rightSide.newsItemPage.transform = "translateX(-600px)";
rightSide.newsPage.transform = "translateX(-600px)";
#endif
this.id = id;
await this.sync();
this.createComment.create();
}
async create() {
await this.commentsMessages.sync();
}
permission() {
this.allow( groups.member, "WRITE" );
this.allow( groups.admin, "WRITE" );
this.allow( groups.visitor, "WRITE" );
this.allow( groups.member, "READ" );
this.allow( groups.admin, "READ" );
this.allow( groups.visitor, "READ" );
}
}

View File

@@ -0,0 +1,17 @@
import newsPrice from '../news.price.js';
import flexbox from '/elements/flexbox.js';
export default class newsPagePrice extends newsPrice, flexbox{
useCustomElement = false;
fontWeight = "bold";
fontSize = 30;
padding = 20;
}

View File

@@ -0,0 +1,16 @@
import button from '/elements/button.js';
export default class saveButton extends button {
label = "Save";
async click( event, object ){
var result = await this.parent.save();
}
}

View File

@@ -0,0 +1,70 @@
import newsTitle from '../news.title.js';
import flexbox from '/elements/flexbox.js';
export default class newsPageTitle extends newsTitle, flexbox{
useCustomElement = false;
fontWeight = "bold";
//padding = 20;
//width = "600px"
//boxSizing = "border-box";
#ifdef MACOS
#ifdef DARK
background = "#282828";
#endif
#ifdef LIGHT
background = "#ffffff";
#endif
#endif
#ifdef WINDOWS
#ifdef DARK
//background = "#202020cc";
#endif
#ifdef LIGHT
//background = "rgb(255 255 255 / 75%)";
#endif
#endif
#ifdef ANDROID
borderRadius = "18px 18px 0 0"
width = "100%"
#ifdef LIGHT
background = "white";
#endif
#endif
fontSize = 30;
padding = 20;
}

View File

@@ -0,0 +1,28 @@
import input from "/elements/input.js";
export default class searchBar extends input {
placeholder = "Search."
async keyup( event ) {
this.value = this.customElement.value;
var value = this.value;
console.log("search input", value);
var newsItems = this.parent.commentsMessages;
await newsItems.search( value );
await newsItems.sync();
}
}

View File

@@ -0,0 +1,17 @@
import os from "./rows/os.js";
import tint from "./rows/tint.js";
import panel from '/elements/panel.js';
export default class appearancePanel extends panel{
flexDirection = "column";
os = new os();
tint = new tint();
}

View File

@@ -0,0 +1,37 @@
import panelRow from '/elements/panel/row.js';
import spinner from '/elements/preloaders/simpleSpinner.js';
import osSelectorList from "./os.selector.list.js";
import osLabel from "./os.label.js";
export default class os extends panelRow{
flexDirection = "row";
label = new osLabel("Os");
osSelector = new osSelectorList();
spinner = new spinner();
create() {
this.osSelector.hide();
}
afterThemeLoad() {
this.spinner.hide()
this.osSelector.show();
}
}

View File

@@ -0,0 +1,8 @@
import label from '/elements/label.js';
export default class osLabel extends label{
flex = "1";
}

View File

@@ -0,0 +1,26 @@
import themeSelector from "../themeSelector.js";
import tools from '/unify/tools.js';
export default class themeOSSelectorItem extends themeSelector{
click() {
var osName = tools.CamelCase( this.selectLabel.text );
this.getRoot().os = osName;
this.parent.updateImages( this.getRoot().tint );
this.parents("appearancePanel").tint.themeTintSelectors.updateImages( osName )
this.highlight();
}
propegateEvent = false;
}

View File

@@ -0,0 +1,44 @@
import themeOSSelector from './os.selector.js'
import tools from '/unify/tools.js';
export default class osSelectorList{
themeWindows = new themeOSSelector("Windows");
themeMacOS = new themeOSSelector("macOS");
//themeAndroid = new themeOSSelector("Android");
updateImages( tint ) {
var camelCaseTint = tools.CamelCase( tint );
this.themeWindows.setImage("/assets/images/themeSelectors/windows" + camelCaseTint + ".png");
this.themeMacOS.setImage("/assets/images/themeSelectors/macos" + camelCaseTint + ".png");
//this.themeAndroid.setImage("/assets/images/themeSelectors/macos" + camelCaseTint + ".png");
}
create() {
this.themeWindows.highlight();
this.themeWindows.setImage('/assets/images/themeSelectors/windowsLight.png');
this.themeMacOS.setImage('/assets/images/themeSelectors/macosLight.png');
//this.themeAndroid.setImage('/assets/images/themeSelectors/macosLight.png');
}
layers = 1;
margin = 4;
marginLeft = "auto";
}

View File

@@ -0,0 +1,38 @@
import panelRow from '/elements/panel/row.js';
import select from '/elements/selectRenderCollection.js';
import spinner from '/elements/preloaders/simpleSpinner.js';
import themeTintSelectors from "./tint.selector.list.js";
import customLabel from "./tint.label.js";
export default class tint extends panelRow{
flexDirection = "row";
label = new customLabel("Appearance");
themeTintSelectors = new themeTintSelectors();
spinner = new spinner();
create() {
this.themeTintSelectors.hide()
}
afterThemeLoad() {
this.spinner.hide()
this.themeTintSelectors.show();
}
}

View File

@@ -0,0 +1,10 @@
import label from '/elements/label.js';
export default class customLabel extends label{
flex = "1";
}

View File

@@ -0,0 +1,24 @@
import themeSelector from "../themeSelector.js";
import tools from '/unify/tools.js';
export default class themeTintSelector extends themeSelector{
click() {
var tintName = tools.CamelCase( this.selectLabel.text );
this.parents("appearancePanel").os.osSelector.updateImages( tintName )
this.highlight();
this.getRoot().tint = tintName;
}
propegateEvent = false;
}

View File

@@ -0,0 +1,46 @@
import themeTintSelector from "./tint.selector.js"
import tools from '/unify/tools.js';
export default class themeTintSelectors{
themeLight = new themeTintSelector("Light");
themeDark = new themeTintSelector("Dark");
updateImages( os ) {
os = os.toLowerCase();
var tint = tools.CamelCase( this.getRoot().tint );
this.themeDark.setImage("/assets/images/themeSelectors/" + os + "Dark.png");
this.themeLight.setImage("/assets/images/themeSelectors/" + os + "Light.png");
this["theme"+tint].highlight();
}
create() {
this.themeDark.highlight();
this.themeDark.setImage('/assets/images/themeSelectors/windowsDark.png');
this.themeLight.setImage('/assets/images/themeSelectors/windowsLight.png');
}
layers = 1;
margin = 4;
marginLeft = "auto";
}

View File

@@ -0,0 +1,41 @@
export default class themaSelectorImage{
cursor = "pointer";
backgroundSize = "cover";
borderRadius = 12;
layers = 1;
width = 80;
height = 80;
margin = 20;
marginBottom = 4;
transition = "1s"
border;
backgroundImage;
lowLight() {
this.border = "none";
}
highlight() {
this.border = "2px solid blue";
}
}

Some files were not shown because too many files have changed in this diff Show More