浏览代码

dev: updates and URL collector

KernelDeimos 12 小时之前
父节点
当前提交
368c20cf57

+ 9 - 3
src/backend/src/api/APIError.js

@@ -51,6 +51,12 @@ module.exports = class APIError {
             status: 400,
             message: () => 'Invalid token'
         },
+        'unrecognized_offering': {
+            status: 400,
+            message: ({ name }) => {
+                return `offering ${quot(name)} was not recognized.`;
+            },
+        },
         // Things
         'disallowed_thing': {
             status: 400,
@@ -65,7 +71,7 @@ module.exports = class APIError {
                         : ''
                 ) + '.'
         },
-        
+
         // Unorganized
         'item_with_same_name_exists': {
             status: 409,
@@ -590,11 +596,11 @@ module.exports = class APIError {
             status: this.status,
         };
     }
-    
+
     querystringize (extra) {
         return new URLSearchParams(this.querystringize_(extra));
     }
-    
+
     querystringize_ (extra) {
         const fields = {};
         for ( const k in this.fields ) {

+ 19 - 0
src/gui/src/UI/Components/JustID.js

@@ -0,0 +1,19 @@
+const Component = use('util.Component');
+
+export default def(class JustID extends Component {
+    static ID = 'ui.component.JustID';
+    static RENDER_MODE = Component.NO_SHADOW;
+
+    static PROPERTIES = {
+        id: { value: undefined },
+    }
+
+    create_template ({ template }) {
+        const size = 24;
+        $(template).html(/*html*/`
+            <div
+                style="height: 358px"
+                id="${this.get('id')}"></div>
+        `);
+    }
+})

+ 19 - 0
src/gui/src/UI/Components/StepView.js

@@ -71,6 +71,21 @@ export default def(class StepView extends Component {
         // now that we're ready, show the wrapper
         $(this.dom_).find('#wrapper').show();
     }
+    
+    add_child (child) {
+        const children = this.get('children');
+        let pos = children.length;
+        child.setAttribute('slot', 'inside');
+        $(child).hide();
+        child.attach(this);
+        
+        return pos;
+    }
+    
+    display (child) {
+        const pos = this.add_child(child);
+        this.goto(pos);
+    }
 
     back () {
         if ( this.get('position') === 0 ) return;
@@ -84,4 +99,8 @@ export default def(class StepView extends Component {
         }
         this.set('position', this.get('position') + 1);
     }
+    
+    goto (pos) {
+        this.set('position', pos);
+    }
 });

+ 7 - 1
src/gui/src/i18n/translations/en.js

@@ -322,7 +322,7 @@ const en = {
         zipping: "Zipping %strong%",
 
         // === 2FA Setup ===
-        setup2fa_1_step_heading: 'Open your authenticator app',      
+        setup2fa_1_step_heading: 'Open your authenticator app',
         setup2fa_1_instructions: `
             You can use any authenticator app that supports the Time-based One-Time Password (TOTP) protocol.
             There are many to choose from, but if you're unsure
@@ -349,6 +349,12 @@ const en = {
         login2fa_use_recovery_code: 'Use a recovery code',
         login2fa_recovery_back: 'Back',
         login2fa_recovery_placeholder: 'XXXXXXXX',
+
+        // Subscriptions
+        'offering.free': 'Use Puter',
+        'offering.pay-puter': 'Pay Puter',
+        'offering.pay-puter-more': 'Pay Puter More',
+        'offering.pay-puter-even-more': 'Pay Puter Even More',
     }
 };
 

文件差异内容过多而无法显示
+ 2 - 0
src/gui/src/icons/subscription.svg


+ 2 - 0
src/gui/src/init_async.js

@@ -21,12 +21,14 @@ logger.info('start -> async initialization');
 
 import './util/TeePromise.js';
 import './util/Component.js';
+import './util/Collector.js';
 import './UI/Components/Frame.js';
 import './UI/Components/Glyph.js';
 import './UI/Components/Spinner.js';
 import './UI/Components/ActionCard.js';
 import './UI/Components/NotifCard.js';
 import './UI/Components/TestView.js';
+import './UI/Components/JustID.js';
 
 logger.info('end -> async initialization');
 globalThis.init_promise.resolve();

+ 66 - 0
src/gui/src/util/Collector.js

@@ -0,0 +1,66 @@
+const CollectorHandle = (key, collector) => ({
+    async get (route) {
+        if ( collector.stored[key] ) return collector.stored[key];
+        return await collector.fetch({ key, method: 'get', route });
+    },
+    async post (route, body) {
+        if ( collector.stored[key] ) return collector.stored[key];
+        return await collector.fetch({ key, method: 'post', route, body });
+    }
+})
+
+// TODO: link this with kv.js for expiration handling
+export default def(class Collector {
+    constructor ({ origin, authToken }) {
+        this.origin = origin;
+        this.authToken = authToken;
+        this.stored = {};
+    }
+
+    to (name) {
+        return CollectorHandle(name, this);
+    }
+
+    whats (key) {
+        return this.stored[key];
+    }
+
+    async get (route) {
+        return await this.fetch({ method: 'get', route });
+    }
+    async post (route, body) {
+        return await this.fetch({ method: 'post', route, body });
+    }
+
+    discard (key) {
+        if ( ! key ) this.stored = {};
+        delete this.stored[key];
+    }
+
+    async fetch (options) {
+        const fetchOptions = {
+            method: options.method,
+            headers: {
+                Authorization: `Bearer ${this.authToken}`,
+                'Content-Type': 'application/json',
+            },
+        };
+
+        if ( options.method === 'post' ) {
+            fetchOptions.body = JSON.stringify(
+                options.body ?? {});
+        }
+
+        const maybe_slash = options.route.startsWith('/')
+            ? '' : '/';
+
+        const resp = await fetch(
+            this.origin +maybe_slash+ options.route,
+            fetchOptions,
+        );
+        const asJSON = await resp.json();
+
+        if ( options.key ) this.stored[options.key] = asJSON;
+        return asJSON;
+    }
+}, 'util.Collector');

+ 6 - 2
src/gui/src/util/Component.js

@@ -183,7 +183,10 @@ export const Component = def(class Component extends HTMLElement {
             this.dom_.appendChild(style);
         }
         if ( this.create_template ) {
-            this.create_template({ template });
+            this.create_template({
+                template,
+                content: template.content,
+            });
         }
         const el = template.content.cloneNode(true);
         return el;
@@ -202,7 +205,8 @@ export const Component = def(class Component extends HTMLElement {
                 }
                 this.values_[name].sub(callback);
                 callback(this.values_[name].get(), {});
-            }
+            },
+            dom: this.dom_,
         };
     }
 });

+ 4 - 4
src/gui/src/util/Placeholder.js

@@ -27,13 +27,13 @@
 /**
  * Placeholder creates a simple element with a unique ID
  * as an HTML string.
- * 
+ *
  * This can be useful where string concatenation is used
  * to build element trees.
- * 
+ *
  * The `replaceWith` method can be used to replace the
  * placeholder with a real element.
- * 
+ *
  * @returns {PlaceholderReturn}
  */
 const Placeholder = def(() => {
@@ -49,7 +49,7 @@ const Placeholder = def(() => {
     };
 }, 'util.Placeholder');
 
-const anti_collision = `94d2cb6b85a1`; // Arbitrary random string
+const anti_collision = `a4d2cb6b85a1`; // Arbitrary random string
 Placeholder.next_id_ = 0;
 Placeholder.get_next_id_ = () => `${anti_collision}_${Placeholder.next_id_++}`;