From 39e73b3f4d5d76a1dfc8fb0a90e7fcba0c0c5ed7 Mon Sep 17 00:00:00 2001 From: ayamoosa Date: Wed, 13 Mar 2024 08:13:52 -0700 Subject: [PATCH 01/24] added menu-aim plugin --- src/lib/jquery.menu-aim.js | 322 +++++++++++++++++++++++++++++++++++++ src/static-assets.js | 1 + 2 files changed, 323 insertions(+) create mode 100644 src/lib/jquery.menu-aim.js diff --git a/src/lib/jquery.menu-aim.js b/src/lib/jquery.menu-aim.js new file mode 100644 index 000000000..1039f5c13 --- /dev/null +++ b/src/lib/jquery.menu-aim.js @@ -0,0 +1,322 @@ +/** + * menu-aim is a jQuery plugin for dropdown menus that can differentiate + * between a user trying hover over a dropdown item vs trying to navigate into + * a submenu's contents. + * + * menu-aim assumes that you have are using a menu with submenus that expand + * to the menu's right. It will fire events when the user's mouse enters a new + * dropdown item *and* when that item is being intentionally hovered over. + * + * __________________________ + * | Monkeys >| Gorilla | + * | Gorillas >| Content | + * | Chimps >| Here | + * |___________|____________| + * + * In the above example, "Gorillas" is selected and its submenu content is + * being shown on the right. Imagine that the user's cursor is hovering over + * "Gorillas." When they move their mouse into the "Gorilla Content" area, they + * may briefly hover over "Chimps." This shouldn't close the "Gorilla Content" + * area. + * + * This problem is normally solved using timeouts and delays. menu-aim tries to + * solve this by detecting the direction of the user's mouse movement. This can + * make for quicker transitions when navigating up and down the menu. The + * experience is hopefully similar to amazon.com/'s "Shop by Department" + * dropdown. + * + * Use like so: + * + * $("#menu").menuAim({ + * activate: $.noop, // fired on row activation + * deactivate: $.noop // fired on row deactivation + * }); + * + * ...to receive events when a menu's row has been purposefully (de)activated. + * + * The following options can be passed to menuAim. All functions execute with + * the relevant row's HTML element as the execution context ('this'): + * + * .menuAim({ + * // Function to call when a row is purposefully activated. Use this + * // to show a submenu's content for the activated row. + * activate: function() {}, + * + * // Function to call when a row is deactivated. + * deactivate: function() {}, + * + * // Function to call when mouse enters a menu row. Entering a row + * // does not mean the row has been activated, as the user may be + * // mousing over to a submenu. + * enter: function() {}, + * + * // Function to call when mouse exits a menu row. + * exit: function() {}, + * + * // Selector for identifying which elements in the menu are rows + * // that can trigger the above events. Defaults to "> li". + * rowSelector: "> li", + * + * // You may have some menu rows that aren't submenus and therefore + * // shouldn't ever need to "activate." If so, filter submenu rows w/ + * // this selector. Defaults to "*" (all elements). + * submenuSelector: "*", + * + * // Direction the submenu opens relative to the main menu. Can be + * // left, right, above, or below. Defaults to "right". + * submenuDirection: "right" + * }); + * + * https://github.com/kamens/jQuery-menu-aim +*/ +(function($) { + + $.fn.menuAim = function(opts) { + // Initialize menu-aim for all elements in jQuery collection + this.each(function() { + init.call(this, opts); + }); + + return this; + }; + + function init(opts) { + var $menu = $(this), + activeRow = null, + mouseLocs = [], + lastDelayLoc = null, + timeoutId = null, + options = $.extend({ + rowSelector: "> li", + submenuSelector: "*", + submenuDirection: "right", + tolerance: 75, // bigger = more forgivey when entering submenu + enter: $.noop, + exit: $.noop, + activate: $.noop, + deactivate: $.noop, + exitMenu: $.noop + }, opts); + + var MOUSE_LOCS_TRACKED = 3, // number of past mouse locations to track + DELAY = 300; // ms delay when user appears to be entering submenu + + /** + * Keep track of the last few locations of the mouse. + */ + var mousemoveDocument = function(e) { + mouseLocs.push({x: e.pageX, y: e.pageY}); + + if (mouseLocs.length > MOUSE_LOCS_TRACKED) { + mouseLocs.shift(); + } + }; + + /** + * Cancel possible row activations when leaving the menu entirely + */ + var mouseleaveMenu = function() { + if (timeoutId) { + clearTimeout(timeoutId); + } + + // If exitMenu is supplied and returns true, deactivate the + // currently active row on menu exit. + if (options.exitMenu(this)) { + if (activeRow) { + options.deactivate(activeRow); + } + + activeRow = null; + } + }; + + /** + * Trigger a possible row activation whenever entering a new row. + */ + var mouseenterRow = function() { + if (timeoutId) { + // Cancel any previous activation delays + clearTimeout(timeoutId); + } + + options.enter(this); + possiblyActivate(this); + }, + mouseleaveRow = function() { + options.exit(this); + }; + + /* + * Immediately activate a row if the user clicks on it. + */ + var clickRow = function() { + activate(this); + }; + + /** + * Activate a menu row. + */ + var activate = function(row) { + if (row == activeRow) { + return; + } + + if (activeRow) { + options.deactivate(activeRow); + } + + options.activate(row); + activeRow = row; + }; + + /** + * Possibly activate a menu row. If mouse movement indicates that we + * shouldn't activate yet because user may be trying to enter + * a submenu's content, then delay and check again later. + */ + var possiblyActivate = function(row) { + var delay = activationDelay(); + + if (delay) { + timeoutId = setTimeout(function() { + possiblyActivate(row); + }, delay); + } else { + activate(row); + } + }; + + /** + * Return the amount of time that should be used as a delay before the + * currently hovered row is activated. + * + * Returns 0 if the activation should happen immediately. Otherwise, + * returns the number of milliseconds that should be delayed before + * checking again to see if the row should be activated. + */ + var activationDelay = function() { + if (!activeRow || !$(activeRow).is(options.submenuSelector)) { + // If there is no other submenu row already active, then + // go ahead and activate immediately. + return 0; + } + + var offset = $menu.offset(), + upperLeft = { + x: offset.left, + y: offset.top - options.tolerance + }, + upperRight = { + x: offset.left + $menu.outerWidth(), + y: upperLeft.y + }, + lowerLeft = { + x: offset.left, + y: offset.top + $menu.outerHeight() + options.tolerance + }, + lowerRight = { + x: offset.left + $menu.outerWidth(), + y: lowerLeft.y + }, + loc = mouseLocs[mouseLocs.length - 1], + prevLoc = mouseLocs[0]; + + if (!loc) { + return 0; + } + + if (!prevLoc) { + prevLoc = loc; + } + + if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x || + prevLoc.y < offset.top || prevLoc.y > lowerRight.y) { + // If the previous mouse location was outside of the entire + // menu's bounds, immediately activate. + return 0; + } + + if (lastDelayLoc && + loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) { + // If the mouse hasn't moved since the last time we checked + // for activation status, immediately activate. + return 0; + } + + // Detect if the user is moving towards the currently activated + // submenu. + // + // If the mouse is heading relatively clearly towards + // the submenu's content, we should wait and give the user more + // time before activating a new row. If the mouse is heading + // elsewhere, we can immediately activate a new row. + // + // We detect this by calculating the slope formed between the + // current mouse location and the upper/lower right points of + // the menu. We do the same for the previous mouse location. + // If the current mouse location's slopes are + // increasing/decreasing appropriately compared to the + // previous's, we know the user is moving toward the submenu. + // + // Note that since the y-axis increases as the cursor moves + // down the screen, we are looking for the slope between the + // cursor and the upper right corner to decrease over time, not + // increase (somewhat counterintuitively). + function slope(a, b) { + return (b.y - a.y) / (b.x - a.x); + }; + + var decreasingCorner = upperRight, + increasingCorner = lowerRight; + + // Our expectations for decreasing or increasing slope values + // depends on which direction the submenu opens relative to the + // main menu. By default, if the menu opens on the right, we + // expect the slope between the cursor and the upper right + // corner to decrease over time, as explained above. If the + // submenu opens in a different direction, we change our slope + // expectations. + if (options.submenuDirection == "left") { + decreasingCorner = lowerLeft; + increasingCorner = upperLeft; + } else if (options.submenuDirection == "below") { + decreasingCorner = lowerRight; + increasingCorner = lowerLeft; + } else if (options.submenuDirection == "above") { + decreasingCorner = upperLeft; + increasingCorner = upperRight; + } + + var decreasingSlope = slope(loc, decreasingCorner), + increasingSlope = slope(loc, increasingCorner), + prevDecreasingSlope = slope(prevLoc, decreasingCorner), + prevIncreasingSlope = slope(prevLoc, increasingCorner); + + if (decreasingSlope < prevDecreasingSlope && + increasingSlope > prevIncreasingSlope) { + // Mouse is moving from previous location towards the + // currently activated submenu. Delay before activating a + // new menu row, because user may be moving into submenu. + lastDelayLoc = loc; + return DELAY; + } + + lastDelayLoc = null; + return 0; + }; + + /** + * Hook up initial menu events + */ + $menu + .mouseleave(mouseleaveMenu) + .find(options.rowSelector) + .mouseenter(mouseenterRow) + .mouseleave(mouseleaveRow) + .click(clickRow); + + $(document).mousemove(mousemoveDocument); + + }; +})(jQuery); \ No newline at end of file diff --git a/src/static-assets.js b/src/static-assets.js index 32e5cf7ee..da8d50a02 100644 --- a/src/static-assets.js +++ b/src/static-assets.js @@ -27,6 +27,7 @@ const lib_paths =[ `/lib/jquery-ui-1.13.2/jquery-ui.min.js`, `/lib/lodash@4.17.21.min.js`, `/lib/jquery.dragster.js`, + '/lib/jquery.menu-aim.js', `/lib/html-entities.js`, `/lib/timeago.min.js`, `/lib/iro.min.js`, From 99ab5cec5b59876e99eae1dc3f4aaa93942df80b Mon Sep 17 00:00:00 2001 From: ayamoosa Date: Wed, 13 Mar 2024 09:21:18 -0700 Subject: [PATCH 02/24] works on right side --- src/UI/UIContextMenu.js | 119 +++++++++++++++++++++++++++---------- src/lib/jquery.menu-aim.js | 2 +- 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/src/UI/UIContextMenu.js b/src/UI/UIContextMenu.js index a6168eda4..bb86aa230 100644 --- a/src/UI/UIContextMenu.js +++ b/src/UI/UIContextMenu.js @@ -149,42 +149,99 @@ function UIContextMenu(options){ return false; }); - // when mouse is over an item - $(contextMenu).find('.context-menu-item').on('mouseover', function (e) { - // mark other items as inactive - $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); - // mark this item as active - $(this).addClass('context-menu-item-active'); - // close any submenu that doesn't belong to this item - $(`.context-menu[data-parent-id="${menu_id}"]`).remove(); - // mark this context menu as active - $(contextMenu).addClass('context-menu-active'); - }) + $(contextMenu).menuAim({ + submenuDirection: function (e){ + // if submenu will open to the left of menu item + if (e.getBoundingClientRect().left + e.getBoundingClientRect().width > window.innerWidth) { + return 'left'; + } else { + return 'right'; + } + }, + activate: function (e) { + + console.log('activate', e) + + // mark other items as inactive + $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); + // mark this item as active + $(e).addClass('context-menu-item-active'); + // close any submenu that doesn't belong to this item + $(`.context-menu[data-parent-id="${menu_id}"]`).remove(); + // mark this context menu as active + $(contextMenu).addClass('context-menu-active'); + + + // activate submenu + // open submenu if applicable + if($(e).hasClass('context-menu-item-submenu')){ + let item_rect_box = e.getBoundingClientRect(); + // open submenu only if it's not already open + if($(`.context-menu[data-id="${menu_id}-${$(e).attr('data-action')}"]`).length === 0){ + // close other submenus + $(`.context-menu[parent-element-id="${menu_id}"]`).remove(); + // open the new submenu + UIContextMenu({ + items: options.items[parseInt($(e).attr('data-action'))].items, + parent_id: menu_id, + is_submenu: true, + id: menu_id + '-' + $(e).attr('data-action'), + position:{ + top: item_rect_box.top - 5, + left: x_pos + item_rect_box.width + 15, + } + }) + } + } + }, + deactivate: function (e) { + console.log('deactivate') + //deactivate submenu + if($(e).hasClass('context-menu-item-submenu')){ + $(`.context-menu[data-id="${menu_id}-${$(e).attr('data-action')}"]`).remove(); + } + } + }); + + + + + // // when mouse is over an item + // $(contextMenu).find('.context-menu-item').on('mouseover', function (e) { + // // mark other items as inactive + // $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); + // // mark this item as active + // $(this).addClass('context-menu-item-active'); + // // close any submenu that doesn't belong to this item + // $(`.context-menu[data-parent-id="${menu_id}"]`).remove(); + // // mark this context menu as active + // $(contextMenu).addClass('context-menu-active'); + // }) // open submenu if applicable - $(`#context-menu-${menu_id} > li.context-menu-item-submenu`).on('mouseover', function (e) { + // $(`#context-menu-${menu_id} > li.context-menu-item-submenu`).on('mouseover', function (e) { - // open submenu only if it's not already open - if($(`.context-menu[data-id="${menu_id}-${$(this).attr('data-action')}"]`).length === 0){ - let item_rect_box = this.getBoundingClientRect(); + // // open submenu only if it's not already open + // if($(`.context-menu[data-id="${menu_id}-${$(this).attr('data-action')}"]`).length === 0){ + // let item_rect_box = this.getBoundingClientRect(); - // close other submenus - $(`.context-menu[parent-element-id="${menu_id}"]`).remove(); + // // close other submenus + // $(`.context-menu[parent-element-id="${menu_id}"]`).remove(); - // open the new submenu - UIContextMenu({ - items: options.items[parseInt($(this).attr('data-action'))].items, - parent_id: menu_id, - is_submenu: true, - id: menu_id + '-' + $(this).attr('data-action'), - position:{ - top: item_rect_box.top - 5, - left: x_pos + item_rect_box.width + 15, - } - }) - } - return false; - }); + // // open the new submenu + // UIContextMenu({ + // items: options.items[parseInt($(this).attr('data-action'))].items, + // parent_id: menu_id, + // is_submenu: true, + // id: menu_id + '-' + $(this).attr('data-action'), + // position:{ + // top: item_rect_box.top - 5, + // left: x_pos + item_rect_box.width + 15, + // } + // }) + // } + // return false; + // }); // useful in cases such as where a menue item is over a window, this prevents from the mousedown event // reaching the window underneath diff --git a/src/lib/jquery.menu-aim.js b/src/lib/jquery.menu-aim.js index 1039f5c13..0c2f82bad 100644 --- a/src/lib/jquery.menu-aim.js +++ b/src/lib/jquery.menu-aim.js @@ -89,7 +89,7 @@ options = $.extend({ rowSelector: "> li", submenuSelector: "*", - submenuDirection: "right", + submenuDirection: $.noop, tolerance: 75, // bigger = more forgivey when entering submenu enter: $.noop, exit: $.noop, From 59f4877ee551e1c57c13b087c32cbef44c93fa67 Mon Sep 17 00:00:00 2001 From: Axorax <78349410+Axorax@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:23:53 +0600 Subject: [PATCH 03/24] update: use any port if all attempts fail --- dev-server.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dev-server.js b/dev-server.js index 30f149d1d..2add59668 100644 --- a/dev-server.js +++ b/dev-server.js @@ -10,15 +10,14 @@ let port = process.env.PORT ?? 4000; // Starting port const maxAttempts = 10; // Maximum number of ports to try const env = argv[2] ?? "dev"; -const startServer = (attempt) => { +const startServer = (attempt, useAnyFreePort = false) => { if (attempt > maxAttempts) { - console.error(chalk.red(`ERROR: Unable to find an available port after ${maxAttempts} attempts.`)); - return; + useAnyFreePort = true; // Use any port that is free } - app.listen(port, () => { + const server = app.listen(useAnyFreePort ? 0 : port, () => { console.log("\n-----------------------------------------------------------\n"); - console.log(`Puter is now live at: `, chalk.underline.blue(`http://localhost:${port}`)); + console.log(`Puter is now live at: `, chalk.underline.blue(`http://localhost:${server.address().port}`)); console.log("\n-----------------------------------------------------------\n"); }).on('error', (err) => { if (err.code === 'EADDRINUSE') { // Check if the error is because the port is already in use From d5ef139db086d2d08e4d6a21670a42be6077bcda Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Thu, 14 Mar 2024 14:15:10 -0400 Subject: [PATCH 04/24] Add checklists for x86 emulation project --- incubator/x86emu/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 incubator/x86emu/README.md diff --git a/incubator/x86emu/README.md b/incubator/x86emu/README.md new file mode 100644 index 000000000..e74e444de --- /dev/null +++ b/incubator/x86emu/README.md @@ -0,0 +1,17 @@ +# Research + Planning for x86 Emulation in Puter + +## Resources +- [copy.sh/v86 docs](https://github.com/copy/v86/blob/master/docs) +- [greenfield github](https://github.com/udevbe/greenfield) + +## TODO + +### Documents to Write + +- [ ] specification for Puter network driver +- [ ] specification for Puter network relay + +### Things to Try + +- [ ] greenfield/wayland/arch/v86 +- [ ] puter-fuse in v86 From 072eec4004edb7a5778b087d8828082c03c0b6a1 Mon Sep 17 00:00:00 2001 From: Sylvain Huguet Date: Fri, 15 Mar 2024 00:15:16 +0100 Subject: [PATCH 05/24] Initial version --- .github/workflows/docker-image.yaml | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/docker-image.yaml diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml new file mode 100644 index 000000000..4490f0a27 --- /dev/null +++ b/.github/workflows/docker-image.yaml @@ -0,0 +1,74 @@ +# +name: Docker Image CI + +# Configures this workflow to run every time a change is pushed to the +# branch called `master`. +on: + push: + branches: ['master'] + pull_request: + branches: ['master'] + schedule: + - cron: "0 2 * * 1-5" + + +# Defines two custom environment variables for the workflow. These are used +# for the Container registry domain, and a name for the Docker image that +# this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the +# latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions + # in this job. + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Uses the `docker/login-action` action to log in to the Container + # registry using the account and password that will publish the packages. + # Once published, the packages are scoped to the account defined here. + - name: Log in to GitHub Package Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) + # to extract tags and labels that will be applied to the specified image. + # The `id` "meta" allows the output of this step to be referenced in + # a subsequent step. The `images` value provides the base name for the + # tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # This step uses the `docker/build-push-action` action to build the + # image, based on your repository's `Dockerfile`. If the build succeeds, + # it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the + # set of files located in the specified path. For more information, see + # "[Usage](https://github.com/docker/build-push-action#usage)" in the + # README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image + # with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From c7850fe18f1d45010c5f0dc9f4f2b0e1ccc35706 Mon Sep 17 00:00:00 2001 From: Sylvain Huguet Date: Fri, 15 Mar 2024 00:40:39 +0100 Subject: [PATCH 06/24] Tweaking the metadata/image tagging mechanism --- .github/workflows/docker-image.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 4490f0a27..60e0c52ef 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -55,6 +55,15 @@ jobs: uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} + type=sha + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} # This step uses the `docker/build-push-action` action to build the # image, based on your repository's `Dockerfile`. If the build succeeds, From cc9d9b10635978e76f237c4596b5f898081a2216 Mon Sep 17 00:00:00 2001 From: Sylvain Huguet Date: Fri, 15 Mar 2024 00:43:02 +0100 Subject: [PATCH 07/24] fix indentation --- .github/workflows/docker-image.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 60e0c52ef..0abd22641 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -56,14 +56,14 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} - type=sha - type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} + type=sha + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} # This step uses the `docker/build-push-action` action to build the # image, based on your repository's `Dockerfile`. If the build succeeds, From 06789da119dc4b26f8f0a000804b6ada7c27dcc0 Mon Sep 17 00:00:00 2001 From: Sylvain Huguet Date: Fri, 15 Mar 2024 10:42:22 +0100 Subject: [PATCH 08/24] Renamed `master` branch to `main` to reflect upstream changes. --- .github/workflows/docker-image.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 0abd22641..67c17c2ac 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -5,11 +5,7 @@ name: Docker Image CI # branch called `master`. on: push: - branches: ['master'] - pull_request: - branches: ['master'] - schedule: - - cron: "0 2 * * 1-5" + branches: ['main'] # Defines two custom environment variables for the workflow. These are used From f6279d020108301424b89852fb08a0a5178e09c8 Mon Sep 17 00:00:00 2001 From: Sylvain Huguet Date: Fri, 15 Mar 2024 10:51:12 +0100 Subject: [PATCH 09/24] Change branch name in comment too. --- .github/workflows/docker-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 67c17c2ac..ba1aa4360 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -2,7 +2,7 @@ name: Docker Image CI # Configures this workflow to run every time a change is pushed to the -# branch called `master`. +# branch called `main`. on: push: branches: ['main'] From f88cfc750f80e3f0ccca45662b23fc2bbe3d40d6 Mon Sep 17 00:00:00 2001 From: ayamoosa Date: Fri, 15 Mar 2024 09:15:40 -0700 Subject: [PATCH 10/24] need to work on submenuDirection reactivity --- src/UI/UIContextMenu.js | 44 ++++++++++++++++++++++++++++++-------- src/lib/jquery.menu-aim.js | 7 +++--- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/UI/UIContextMenu.js b/src/UI/UIContextMenu.js index bb86aa230..c4852dcc2 100644 --- a/src/UI/UIContextMenu.js +++ b/src/UI/UIContextMenu.js @@ -149,23 +149,45 @@ function UIContextMenu(options){ return false; }); + // initialize menuAim plugin $(contextMenu).menuAim({ - submenuDirection: function (e){ - // if submenu will open to the left of menu item - if (e.getBoundingClientRect().left + e.getBoundingClientRect().width > window.innerWidth) { - return 'left'; - } else { - return 'right'; - } + submenuDirection: function(){ + //if this is not a submenu + // if(!options.is_submenu){ + // if submenu is going to be on the right of the main menu + if(x_pos + $(contextMenu).width() + 10 > window.innerWidth){ + console.log('right') + return "right"; + } else { + console.log('left') + return "left"; + } + // } }, activate: function (e) { + + //if submenu opens on right make submenuDirection right + // if($(contextMenu).offset().left + $(contextMenu).width() + 10 > window.innerWidth){ + // $(contextMenu).menuAim({ + // submenuDirection: 'right' + // }); + // } else { + // $(contextMenu).menuAim({ + // submenuDirection: 'left' + // }); + // } + // hover over an item + + + + let item = $(e).closest('.context-menu-item'); console.log('activate', e) // mark other items as inactive $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); // mark this item as active - $(e).addClass('context-menu-item-active'); + $(item).addClass('context-menu-item-active'); // close any submenu that doesn't belong to this item $(`.context-menu[data-parent-id="${menu_id}"]`).remove(); // mark this context menu as active @@ -173,6 +195,7 @@ function UIContextMenu(options){ // activate submenu + // open submenu if applicable if($(e).hasClass('context-menu-item-submenu')){ let item_rect_box = e.getBoundingClientRect(); @@ -193,6 +216,7 @@ function UIContextMenu(options){ }) } } + }, deactivate: function (e) { console.log('deactivate') @@ -284,4 +308,6 @@ window.select_ctxmenu_item = function ($ctxmenu_item){ $($ctxmenu_item).addClass('context-menu-item-active'); } -export default UIContextMenu; \ No newline at end of file +export default UIContextMenu; + + diff --git a/src/lib/jquery.menu-aim.js b/src/lib/jquery.menu-aim.js index 0c2f82bad..471053400 100644 --- a/src/lib/jquery.menu-aim.js +++ b/src/lib/jquery.menu-aim.js @@ -166,6 +166,7 @@ options.deactivate(activeRow); } + options.activate(row); activeRow = row; }; @@ -277,13 +278,13 @@ // corner to decrease over time, as explained above. If the // submenu opens in a different direction, we change our slope // expectations. - if (options.submenuDirection == "left") { + if (options.submenuDirection() == "left") { decreasingCorner = lowerLeft; increasingCorner = upperLeft; - } else if (options.submenuDirection == "below") { + } else if (options.submenuDirection() == "below") { decreasingCorner = lowerRight; increasingCorner = lowerLeft; - } else if (options.submenuDirection == "above") { + } else if (options.submenuDirection() == "above") { decreasingCorner = upperLeft; increasingCorner = upperRight; } From 8ec0e63d7b3ffca72b346e06c5b247896766e8ff Mon Sep 17 00:00:00 2001 From: ayamoosa Date: Fri, 15 Mar 2024 12:31:40 -0700 Subject: [PATCH 11/24] fixed left/right issue. Feature is ready --- src/UI/UIContextMenu.js | 78 ++++++----------------------------------- 1 file changed, 10 insertions(+), 68 deletions(-) diff --git a/src/UI/UIContextMenu.js b/src/UI/UIContextMenu.js index c4852dcc2..ce0ab8f80 100644 --- a/src/UI/UIContextMenu.js +++ b/src/UI/UIContextMenu.js @@ -145,45 +145,28 @@ function UIContextMenu(options){ $(contextMenu).remove(); }); } - return false; }); - // initialize menuAim plugin + // initialize menuAim plugin (../libs/jquery.menu-aim.js) $(contextMenu).menuAim({ submenuDirection: function(){ - //if this is not a submenu - // if(!options.is_submenu){ - // if submenu is going to be on the right of the main menu - if(x_pos + $(contextMenu).width() + 10 > window.innerWidth){ - console.log('right') + //if not submenu + if(!options.is_submenu){ + // if submenu left postiton is greater than main menu left position + if($(contextMenu).offset().left + 2 * $(contextMenu).width() + 15 < window.innerWidth ){ return "right"; } else { - console.log('left') return "left"; } - // } + } }, + //activates item when mouse enters depending in mouse position and direction activate: function (e) { - - //if submenu opens on right make submenuDirection right - // if($(contextMenu).offset().left + $(contextMenu).width() + 10 > window.innerWidth){ - // $(contextMenu).menuAim({ - // submenuDirection: 'right' - // }); - // } else { - // $(contextMenu).menuAim({ - // submenuDirection: 'left' - // }); - // } - // hover over an item - - + //activate items let item = $(e).closest('.context-menu-item'); - console.log('activate', e) - // mark other items as inactive $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); // mark this item as active @@ -195,7 +178,6 @@ function UIContextMenu(options){ // activate submenu - // open submenu if applicable if($(e).hasClass('context-menu-item-submenu')){ let item_rect_box = e.getBoundingClientRect(); @@ -216,8 +198,8 @@ function UIContextMenu(options){ }) } } - }, + //deactivates row when mouse leavess deactivate: function (e) { console.log('deactivate') //deactivate submenu @@ -227,47 +209,7 @@ function UIContextMenu(options){ } }); - - - - // // when mouse is over an item - // $(contextMenu).find('.context-menu-item').on('mouseover', function (e) { - // // mark other items as inactive - // $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); - // // mark this item as active - // $(this).addClass('context-menu-item-active'); - // // close any submenu that doesn't belong to this item - // $(`.context-menu[data-parent-id="${menu_id}"]`).remove(); - // // mark this context menu as active - // $(contextMenu).addClass('context-menu-active'); - // }) - - // open submenu if applicable - // $(`#context-menu-${menu_id} > li.context-menu-item-submenu`).on('mouseover', function (e) { - - // // open submenu only if it's not already open - // if($(`.context-menu[data-id="${menu_id}-${$(this).attr('data-action')}"]`).length === 0){ - // let item_rect_box = this.getBoundingClientRect(); - - // // close other submenus - // $(`.context-menu[parent-element-id="${menu_id}"]`).remove(); - - // // open the new submenu - // UIContextMenu({ - // items: options.items[parseInt($(this).attr('data-action'))].items, - // parent_id: menu_id, - // is_submenu: true, - // id: menu_id + '-' + $(this).attr('data-action'), - // position:{ - // top: item_rect_box.top - 5, - // left: x_pos + item_rect_box.width + 15, - // } - // }) - // } - // return false; - // }); - - // useful in cases such as where a menue item is over a window, this prevents from the mousedown event + // useful in cases such as where a menu item is over a window, this prevents from the mousedown event // reaching the window underneath $(`#context-menu-${menu_id} > li:not(.context-menu-item-disabled)`).on('mousedown', function (e) { e.preventDefault(); From a5584317c8d001d6e47f2e45798be8ce7183a155 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Fri, 15 Mar 2024 16:53:05 -0700 Subject: [PATCH 12/24] Allow apps to resize/reposition their windows The resizing and repositioning is safe from abuse in that the window's position and size cannot cause it to escape the viewport. More could be done here, e.g. rate limit resize/repos. I will request rate-limiting in a separate issue. --- src/IPC.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/IPC.js b/src/IPC.js index 18335d90d..5520ee006 100644 --- a/src/IPC.js +++ b/src/IPC.js @@ -333,7 +333,6 @@ window.addEventListener('message', async (event) => { initiating_app_uuid: app_uuid, }); } - //-------------------------------------------------------- // setWindowTitle //-------------------------------------------------------- @@ -347,6 +346,98 @@ window.addEventListener('message', async (event) => { }, '*'); } //-------------------------------------------------------- + // setWindowWidth + //-------------------------------------------------------- + else if(event.data.msg === 'setWindowWidth' && event.data.width !== undefined){ + event.data.width = parseFloat(event.data.width); + // must be at least 200 + if(event.data.width < 200) + event.data.width = 200; + // set window width + $($el_parent_window).css('width', event.data.width); + // send confirmation to requester window + target_iframe.contentWindow.postMessage({ + original_msg_id: msg_id, + }, '*'); + } + //-------------------------------------------------------- + // setWindowHeight + //-------------------------------------------------------- + else if(event.data.msg === 'setWindowHeight' && event.data.height !== undefined){ + event.data.height = parseFloat(event.data.height); + // must be at least 200 + if(event.data.height < 200) + event.data.height = 200; + + // convert to number and set + $($el_parent_window).css('height', event.data.height); + + // send confirmation to requester window + target_iframe.contentWindow.postMessage({ + original_msg_id: msg_id, + }, '*'); + } + //-------------------------------------------------------- + // setWindowSize + //-------------------------------------------------------- + else if(event.data.msg === 'setWindowSize' && (event.data.width !== undefined || event.data.height !== undefined)){ + // convert to number and set + if(event.data.width !== undefined){ + event.data.width = parseFloat(event.data.width); + // must be at least 200 + if(event.data.width < 200) + event.data.width = 200; + $($el_parent_window).css('width', event.data.width); + } + + if(event.data.height !== undefined){ + event.data.height = parseFloat(event.data.height); + // must be at least 200 + if(event.data.height < 200) + event.data.height = 200; + $($el_parent_window).css('height', event.data.height); + } + + // send confirmation to requester window + target_iframe.contentWindow.postMessage({ + original_msg_id: msg_id, + }, '*'); + } + //-------------------------------------------------------- + // setWindowPosition + //-------------------------------------------------------- + else if(event.data.msg === 'setWindowPosition' && (event.data.x !== undefined || event.data.y !== undefined)){ + // convert to number and set + if(event.data.x !== undefined){ + event.data.x = parseFloat(event.data.x); + // we don't want the window to go off the left edge of the screen + if(event.data.x < 0) + event.data.x = 0; + // we don't want the window to go off the right edge of the screen + if(event.data.x > window.innerWidth - 100) + event.data.x = window.innerWidth - 100; + // set window left + $($el_parent_window).css('left', parseFloat(event.data.x)); + } + + if(event.data.y !== undefined){ + event.data.y = parseFloat(event.data.y); + // we don't want the window to go off the top edge of the screen + if(event.data.y < window.taskbar_height) + event.data.y = window.taskbar_height; + // we don't want the window to go off the bottom edge of the screen + if(event.data.y > window.innerHeight - 100) + event.data.y = window.innerHeight - 100; + // set window top + $($el_parent_window).css('top', parseFloat(event.data.y)); + } + + // send confirmation to requester window + target_iframe.contentWindow.postMessage({ + original_msg_id: msg_id, + }, '*'); + } + //-------------------------------------------------------- // watchItem //-------------------------------------------------------- else if(event.data.msg === 'watchItem' && event.data.item_uid !== undefined){ From 4719d7e07676efab5ce8c024e1e76ac2a6cf84eb Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Fri, 15 Mar 2024 17:16:54 -0700 Subject: [PATCH 13/24] Update UIContextMenu.js --- src/UI/UIContextMenu.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UI/UIContextMenu.js b/src/UI/UIContextMenu.js index ce0ab8f80..20491ed3f 100644 --- a/src/UI/UIContextMenu.js +++ b/src/UI/UIContextMenu.js @@ -201,7 +201,6 @@ function UIContextMenu(options){ }, //deactivates row when mouse leavess deactivate: function (e) { - console.log('deactivate') //deactivate submenu if($(e).hasClass('context-menu-item-submenu')){ $(`.context-menu[data-id="${menu_id}-${$(e).attr('data-action')}"]`).remove(); From 06b075b1a44b75f2060ae75abc1b403605caade5 Mon Sep 17 00:00:00 2001 From: meetqy Date: Sat, 16 Mar 2024 13:48:11 +0800 Subject: [PATCH 14/24] fix: #90 --- src/globals.js | 132 +++++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 58 deletions(-) diff --git a/src/globals.js b/src/globals.js index f5ff998ad..222e779ce 100644 --- a/src/globals.js +++ b/src/globals.js @@ -7,17 +7,17 @@ * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -window.clipboard_op = ''; +window.clipboard_op = ""; window.clipboard = []; window.actions_history = []; window.window_nav_history = {}; @@ -43,63 +43,65 @@ window.mouseX = 0; window.mouseY = 0; // get all logged-in users -try{ - window.logged_in_users = JSON.parse(localStorage.getItem("logged_in_users")); -}catch(e){ - window.logged_in_users = []; +try { + window.logged_in_users = JSON.parse(localStorage.getItem("logged_in_users")); +} catch (e) { + window.logged_in_users = []; } -if(window.logged_in_users === null) - window.logged_in_users = []; +if (window.logged_in_users === null) window.logged_in_users = []; // this sessions's user window.auth_token = localStorage.getItem("auth_token"); -try{ - window.user = JSON.parse(localStorage.getItem("user")); -}catch(e){ - window.user = null; +try { + window.user = JSON.parse(localStorage.getItem("user")); +} catch (e) { + window.user = null; } // in case this is the first time user is visiting multi-user feature -if(window.logged_in_users.length === 0 && window.user !== null){ - let tuser = window.user; - tuser.auth_token = window.auth_token - window.logged_in_users.push(tuser); - localStorage.setItem("logged_in_users", window.logged_in_users); +if (window.logged_in_users.length === 0 && window.user !== null) { + let tuser = window.user; + tuser.auth_token = window.auth_token; + window.logged_in_users.push(tuser); + localStorage.setItem("logged_in_users", window.logged_in_users); } window.last_window_zindex = 1; // first visit tracker -window.first_visit_ever = localStorage.getItem("has_visited_before") === null ? true : false; +window.first_visit_ever = + localStorage.getItem("has_visited_before") === null ? true : false; localStorage.setItem("has_visited_before", true); // system paths -if(window.user !== undefined && window.user !== null){ - window.desktop_path = '/' + window.user.username + '/Desktop'; - window.trash_path = '/' + window.user.username + '/Trash'; - window.appdata_path = '/' + window.user.username + '/AppData'; - window.documents_path = '/' + window.user.username + '/Documents'; - window.pictures_path = '/' + window.user.username + '/Photos'; - window.videos_path = '/' + window.user.username + '/Videos'; - window.audio_path = '/' + window.user.username + '/Audio'; - window.home_path = '/' + window.user.username; +if (window.user !== undefined && window.user !== null) { + window.desktop_path = "/" + window.user.username + "/Desktop"; + window.trash_path = "/" + window.user.username + "/Trash"; + window.appdata_path = "/" + window.user.username + "/AppData"; + window.documents_path = "/" + window.user.username + "/Documents"; + window.pictures_path = "/" + window.user.username + "/Photos"; + window.videos_path = "/" + window.user.username + "/Videos"; + window.audio_path = "/" + window.user.username + "/Audio"; + window.home_path = "/" + window.user.username; } -window.root_dirname = 'Puter'; +window.root_dirname = "Puter"; // user preferences, persisted across sessions, cached in localStorage try { - window.user_preferences = JSON.parse(localStorage.getItem('user_preferences')) -}catch(e){ - window.user_preferences = null; + window.user_preferences = JSON.parse( + localStorage.getItem("user_preferences") + ); +} catch (e) { + window.user_preferences = null; } // default values if (window.user_preferences === null) { - window.user_preferences = { - show_hidden_files: false, - } + window.user_preferences = { + show_hidden_files: false, + }; } -window.window_stack = [] +window.window_stack = []; window.toolbar_height = 30; window.default_taskbar_height = 50; window.taskbar_height = window.default_taskbar_height; @@ -112,43 +114,57 @@ window.operation_id = 0; window.operation_cancelled = []; window.last_enter_pressed_to_rename_ts = 0; window.window_counter = 0; -window.keypress_item_seach_term = ''; +window.keypress_item_seach_term = ""; window.keypress_item_seach_buffer_timeout = undefined; window.first_visit_animation = false; window.show_twitter_link = true; window.animate_window_opening = true; window.animate_window_closing = true; -window.desktop_loading_fade_delay = (window.first_visit_ever && first_visit_animation ? 6000 : 1000); +window.desktop_loading_fade_delay = + window.first_visit_ever && first_visit_animation ? 6000 : 1000; window.watchItems = []; window.appdata_signatures = {}; window.appCallbackFunctions = []; // 'Launch' apps window.launch_apps = []; -window.launch_apps.recent = [] -window.launch_apps.recommended = [] +window.launch_apps.recent = []; +window.launch_apps.recommended = []; // Is puter being loaded inside an iframe? if (window.location !== window.parent.location) { - window.is_embedded = true; - // taskbar is not needed in embedded mode - window.taskbar_height = 0; + window.is_embedded = true; + // taskbar is not needed in embedded mode + window.taskbar_height = 0; } else { - window.is_embedded = false; + window.is_embedded = false; } // calculate desktop height and width -window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; +window.desktop_height = + window.innerHeight - window.toolbar_height - window.taskbar_height; window.desktop_width = window.innerWidth; // recalculate desktop height and width on window resize -$( window ).on( "resize", function() { - window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; - window.desktop_width = window.innerWidth; +$(window).on("resize", function () { + const radio = window.desktop_width / window.innerWidth; + + window.desktop_height = + window.innerHeight - window.toolbar_height - window.taskbar_height; + window.desktop_width = window.innerWidth; + + const { top } = $(".window-login").position(); + + const width = $(".window-login").width(); + + $(".window-login").css({ + left: (window.desktop_width - width) / 2, + top: top / radio, + }); }); - + // for now `active_element` is basically the last element that was clicked, -// later on though (todo) `active_element` will also be set by keyboard movements +// later on though (todo) `active_element` will also be set by keyboard movements // such as arrow keys, tab key, ... and when creating new windows... window.active_element = null; @@ -159,7 +175,7 @@ window.launch_recent_apps_count = 10; // if yes, which one? window.current_active_snap_zone = undefined; -// +// window.is_fullpage_mode = false; window.window_border_radius = 4; @@ -167,10 +183,10 @@ window.window_border_radius = 4; window.sites = []; window.feature_flags = { - // if true, the user will be able to create shortcuts to files and directories - create_shortcut: true, - // if true, the user will be asked to confirm before navigating away from Puter only if there is at least one window open - prompt_user_when_navigation_away_from_puter: false, - // if true, the user will be able to zip and download directories - download_directory: true, -} + // if true, the user will be able to create shortcuts to files and directories + create_shortcut: true, + // if true, the user will be asked to confirm before navigating away from Puter only if there is at least one window open + prompt_user_when_navigation_away_from_puter: false, + // if true, the user will be able to zip and download directories + download_directory: true, +}; From 404a6a5aa56060d31f02a6ca3e9bc3c73abf06cd Mon Sep 17 00:00:00 2001 From: meetqy Date: Sat, 16 Mar 2024 13:53:25 +0800 Subject: [PATCH 15/24] format --- src/globals.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/globals.js b/src/globals.js index 222e779ce..11f2cb31d 100644 --- a/src/globals.js +++ b/src/globals.js @@ -147,6 +147,7 @@ window.desktop_width = window.innerWidth; // recalculate desktop height and width on window resize $(window).on("resize", function () { + const radio = window.desktop_width / window.innerWidth; window.desktop_height = From 68cd3f420e70c0162f0197e6dd977367050e0e45 Mon Sep 17 00:00:00 2001 From: meetqy Date: Sat, 16 Mar 2024 13:56:36 +0800 Subject: [PATCH 16/24] code format --- src/globals.js | 142 +++++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 75 deletions(-) diff --git a/src/globals.js b/src/globals.js index 11f2cb31d..5e31a7c3d 100644 --- a/src/globals.js +++ b/src/globals.js @@ -7,17 +7,17 @@ * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -window.clipboard_op = ""; +window.clipboard_op = ''; window.clipboard = []; window.actions_history = []; window.window_nav_history = {}; @@ -43,65 +43,63 @@ window.mouseX = 0; window.mouseY = 0; // get all logged-in users -try { - window.logged_in_users = JSON.parse(localStorage.getItem("logged_in_users")); -} catch (e) { - window.logged_in_users = []; +try{ + window.logged_in_users = JSON.parse(localStorage.getItem("logged_in_users")); +}catch(e){ + window.logged_in_users = []; } -if (window.logged_in_users === null) window.logged_in_users = []; +if(window.logged_in_users === null) + window.logged_in_users = []; // this sessions's user window.auth_token = localStorage.getItem("auth_token"); -try { - window.user = JSON.parse(localStorage.getItem("user")); -} catch (e) { - window.user = null; +try{ + window.user = JSON.parse(localStorage.getItem("user")); +}catch(e){ + window.user = null; } // in case this is the first time user is visiting multi-user feature -if (window.logged_in_users.length === 0 && window.user !== null) { - let tuser = window.user; - tuser.auth_token = window.auth_token; - window.logged_in_users.push(tuser); - localStorage.setItem("logged_in_users", window.logged_in_users); +if(window.logged_in_users.length === 0 && window.user !== null){ + let tuser = window.user; + tuser.auth_token = window.auth_token + window.logged_in_users.push(tuser); + localStorage.setItem("logged_in_users", window.logged_in_users); } window.last_window_zindex = 1; // first visit tracker -window.first_visit_ever = - localStorage.getItem("has_visited_before") === null ? true : false; +window.first_visit_ever = localStorage.getItem("has_visited_before") === null ? true : false; localStorage.setItem("has_visited_before", true); // system paths -if (window.user !== undefined && window.user !== null) { - window.desktop_path = "/" + window.user.username + "/Desktop"; - window.trash_path = "/" + window.user.username + "/Trash"; - window.appdata_path = "/" + window.user.username + "/AppData"; - window.documents_path = "/" + window.user.username + "/Documents"; - window.pictures_path = "/" + window.user.username + "/Photos"; - window.videos_path = "/" + window.user.username + "/Videos"; - window.audio_path = "/" + window.user.username + "/Audio"; - window.home_path = "/" + window.user.username; +if(window.user !== undefined && window.user !== null){ + window.desktop_path = '/' + window.user.username + '/Desktop'; + window.trash_path = '/' + window.user.username + '/Trash'; + window.appdata_path = '/' + window.user.username + '/AppData'; + window.documents_path = '/' + window.user.username + '/Documents'; + window.pictures_path = '/' + window.user.username + '/Photos'; + window.videos_path = '/' + window.user.username + '/Videos'; + window.audio_path = '/' + window.user.username + '/Audio'; + window.home_path = '/' + window.user.username; } -window.root_dirname = "Puter"; +window.root_dirname = 'Puter'; // user preferences, persisted across sessions, cached in localStorage try { - window.user_preferences = JSON.parse( - localStorage.getItem("user_preferences") - ); -} catch (e) { - window.user_preferences = null; + window.user_preferences = JSON.parse(localStorage.getItem('user_preferences')) +}catch(e){ + window.user_preferences = null; } // default values if (window.user_preferences === null) { - window.user_preferences = { - show_hidden_files: false, - }; + window.user_preferences = { + show_hidden_files: false, + } } -window.window_stack = []; +window.window_stack = [] window.toolbar_height = 30; window.default_taskbar_height = 50; window.taskbar_height = window.default_taskbar_height; @@ -114,58 +112,52 @@ window.operation_id = 0; window.operation_cancelled = []; window.last_enter_pressed_to_rename_ts = 0; window.window_counter = 0; -window.keypress_item_seach_term = ""; +window.keypress_item_seach_term = ''; window.keypress_item_seach_buffer_timeout = undefined; window.first_visit_animation = false; window.show_twitter_link = true; window.animate_window_opening = true; window.animate_window_closing = true; -window.desktop_loading_fade_delay = - window.first_visit_ever && first_visit_animation ? 6000 : 1000; +window.desktop_loading_fade_delay = (window.first_visit_ever && first_visit_animation ? 6000 : 1000); window.watchItems = []; window.appdata_signatures = {}; window.appCallbackFunctions = []; // 'Launch' apps window.launch_apps = []; -window.launch_apps.recent = []; -window.launch_apps.recommended = []; +window.launch_apps.recent = [] +window.launch_apps.recommended = [] // Is puter being loaded inside an iframe? if (window.location !== window.parent.location) { - window.is_embedded = true; - // taskbar is not needed in embedded mode - window.taskbar_height = 0; + window.is_embedded = true; + // taskbar is not needed in embedded mode + window.taskbar_height = 0; } else { - window.is_embedded = false; + window.is_embedded = false; } // calculate desktop height and width -window.desktop_height = - window.innerHeight - window.toolbar_height - window.taskbar_height; +window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; window.desktop_width = window.innerWidth; // recalculate desktop height and width on window resize -$(window).on("resize", function () { - - const radio = window.desktop_width / window.innerWidth; - - window.desktop_height = - window.innerHeight - window.toolbar_height - window.taskbar_height; - window.desktop_width = window.innerWidth; - - const { top } = $(".window-login").position(); - - const width = $(".window-login").width(); - - $(".window-login").css({ - left: (window.desktop_width - width) / 2, - top: top / radio, - }); +$( window ).on( "resize", function() { + const radio = window.desktop_width / window.innerWidth; + + window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; + window.desktop_width = window.innerWidth; + + const { top } = $(".window-login").position(); + const width = $(".window-login").width(); + $(".window-login").css({ + left: (window.desktop_width - width) / 2, + top: top / radio, + }); }); - + // for now `active_element` is basically the last element that was clicked, -// later on though (todo) `active_element` will also be set by keyboard movements +// later on though (todo) `active_element` will also be set by keyboard movements // such as arrow keys, tab key, ... and when creating new windows... window.active_element = null; @@ -176,7 +168,7 @@ window.launch_recent_apps_count = 10; // if yes, which one? window.current_active_snap_zone = undefined; -// +// window.is_fullpage_mode = false; window.window_border_radius = 4; @@ -184,10 +176,10 @@ window.window_border_radius = 4; window.sites = []; window.feature_flags = { - // if true, the user will be able to create shortcuts to files and directories - create_shortcut: true, - // if true, the user will be asked to confirm before navigating away from Puter only if there is at least one window open - prompt_user_when_navigation_away_from_puter: false, - // if true, the user will be able to zip and download directories - download_directory: true, -}; + // if true, the user will be able to create shortcuts to files and directories + create_shortcut: true, + // if true, the user will be asked to confirm before navigating away from Puter only if there is at least one window open + prompt_user_when_navigation_away_from_puter: false, + // if true, the user will be able to zip and download directories + download_directory: true, +} From 509a6cbd314fa843287b1ac9ff6d0ab52060739d Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sat, 16 Mar 2024 12:08:11 -0700 Subject: [PATCH 17/24] refactor to account for signup window and the location of the code --- src/globals.js | 15 --------------- src/initgui.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/globals.js b/src/globals.js index 5e31a7c3d..3c443394d 100644 --- a/src/globals.js +++ b/src/globals.js @@ -141,21 +141,6 @@ if (window.location !== window.parent.location) { window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; window.desktop_width = window.innerWidth; -// recalculate desktop height and width on window resize -$( window ).on( "resize", function() { - const radio = window.desktop_width / window.innerWidth; - - window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; - window.desktop_width = window.innerWidth; - - const { top } = $(".window-login").position(); - const width = $(".window-login").width(); - $(".window-login").css({ - left: (window.desktop_width - width) / 2, - top: top / radio, - }); -}); - // for now `active_element` is basically the last element that was clicked, // later on though (todo) `active_element` will also be set by keyboard movements // such as arrow keys, tab key, ... and when creating new windows... diff --git a/src/initgui.js b/src/initgui.js index 097dd0374..121594f4a 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -1976,4 +1976,32 @@ function requestOpenerOrigin() { $(document).on('click', '.generic-close-window-button', function(e){ $(this).closest('.window').close(); +}); + +// Re-calculate desktop height and width on window resize and re-position the login and signup windows +$(window).on("resize", function () { + // If host env is popup, don't continue because the popup window has its own resize requirements. + if (window.embedded_in_popup) + return; + + const ratio = window.desktop_width / window.innerWidth; + + window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height; + window.desktop_width = window.innerWidth; + + // Re-center the login window + const top = $(".window-login").position()?.top; + const width = $(".window-login").width(); + $(".window-login").css({ + left: (window.desktop_width - width) / 2, + top: top / ratio, + }); + + // Re-center the create account window + const top2 = $(".window-signup").position()?.top; + const width2 = $(".window-signup").width(); + $(".window-signup").css({ + left: (window.desktop_width - width2) / 2, + top: top2 / ratio, + }); }); \ No newline at end of file From 8100edbef9367bbb33a0ba6e5847dec731f43744 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sat, 16 Mar 2024 19:57:33 -0700 Subject: [PATCH 18/24] implement query param passing between browser window and app iframe --- src/UI/UIDesktop.js | 1 + src/helpers.js | 9 +++++++++ src/initgui.js | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index 2926f433d..f16dbb2f0 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -930,6 +930,7 @@ async function UIDesktop(options){ name: app_launched_from_url, readURL: qparams.get('readURL'), maximized: qparams.get('maximized'), + params: app_query_params ?? [], is_fullpage: window.is_fullpage_mode, window_options: { stay_on_top: false, diff --git a/src/helpers.js b/src/helpers.js index a12fb405a..753f06202 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1904,6 +1904,7 @@ window.launch_app = async (options)=>{ // add app_instance_id to URL iframe_url.searchParams.append('puter.app_instance_id', uuid); + // add app_id to URL iframe_url.searchParams.append('puter.app.id', app_info.uuid); @@ -1939,6 +1940,14 @@ window.launch_app = async (options)=>{ else if(options.token){ iframe_url.searchParams.append('puter.auth.token', options.token); } + + // if options.params is set, add them to the URL as query params + if(options.params && options.params.length > 0){ + for (const property in options.params) { + iframe_url.searchParams.append(property, options.params[property]); + } + } + // Try to acquire app token from the server else{ let response = await fetch(window.api_origin + "/auth/get-user-app-token", { diff --git a/src/initgui.js b/src/initgui.js index 121594f4a..2b9392445 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -76,6 +76,13 @@ window.initgui = async function(){ const url_paths = window.location.pathname.split('/').filter(element => element); if(url_paths[0]?.toLocaleLowerCase() === 'app' && url_paths[1]){ window.app_launched_from_url = url_paths[1]; + + // get query params, any param that doesn't start with 'puter.' will be passed to the app + window.app_query_params = {}; + for (let [key, value] of url_query_params) { + if(!key.startsWith('puter.')) + app_query_params[key] = value; + } } //-------------------------------------------------------------------------------------- From ec984ac81dae3cfdca338d8b2babed2c8a635cec Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sat, 16 Mar 2024 19:59:36 -0700 Subject: [PATCH 19/24] Remove redundant logic --- src/helpers.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index 753f06202..abd9e422a 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1941,13 +1941,6 @@ window.launch_app = async (options)=>{ iframe_url.searchParams.append('puter.auth.token', options.token); } - // if options.params is set, add them to the URL as query params - if(options.params && options.params.length > 0){ - for (const property in options.params) { - iframe_url.searchParams.append(property, options.params[property]); - } - } - // Try to acquire app token from the server else{ let response = await fetch(window.api_origin + "/auth/get-user-app-token", { From f3e4a12e57c5c89d8db6da93bc3eb8bdff723678 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sat, 16 Mar 2024 20:13:48 -0700 Subject: [PATCH 20/24] Refactor `helpers.js` to reduce file size --- src/UI/UIDesktop.js | 3 +- src/UI/UIWindow.js | 3 +- src/helpers.js | 87 ------------------- .../determine_active_container_parent.js | 43 +++++++++ src/helpers/new_context_menu_item.js | 78 +++++++++++++++++ src/initgui.js | 1 + 6 files changed, 126 insertions(+), 89 deletions(-) create mode 100644 src/helpers/determine_active_container_parent.js create mode 100644 src/helpers/new_context_menu_item.js diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index f16dbb2f0..c25b69fd2 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -33,6 +33,7 @@ import UIWindowLogin from "./UIWindowLogin.js" import UIWindowQR from "./UIWindowQR.js" import UIWindowRefer from "./UIWindowRefer.js" import UITaskbar from "./UITaskbar.js" +import new_context_menu_item from "../helpers/new_context_menu_item.js" async function UIDesktop(options){ let h = ''; @@ -707,7 +708,7 @@ async function UIDesktop(options){ // ------------------------------------------- // New File // ------------------------------------------- - window.new_context_menu_item(desktop_path, el_desktop), + new_context_menu_item(desktop_path, el_desktop), // ------------------------------------------- // - // ------------------------------------------- diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js index 3a90926fe..6e5340b7a 100644 --- a/src/UI/UIWindow.js +++ b/src/UI/UIWindow.js @@ -24,6 +24,7 @@ import UITaskbarItem from './UITaskbarItem.js'; import UIWindowLogin from './UIWindowLogin.js'; import UIWindowPublishWebsite from './UIWindowPublishWebsite.js'; import UIWindowItemProperties from './UIWindowItemProperties.js'; +import new_context_menu_item from '../helpers/new_context_menu_item.js'; const el_body = document.getElementsByTagName('body')[0]; @@ -1898,7 +1899,7 @@ async function UIWindow(options) { // ------------------------------------------- // New // ------------------------------------------- - window.new_context_menu_item($(el_window).attr('data-path'), el_window_body), + new_context_menu_item($(el_window).attr('data-path'), el_window_body), // ------------------------------------------- // - // ------------------------------------------- diff --git a/src/helpers.js b/src/helpers.js index abd9e422a..7b0b795ed 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1979,13 +1979,6 @@ window.launch_app = async (options)=>{ window_class: 'window-app', update_window_url: true, app_uuid: app_info.uuid ?? app_info.uid, - // has_head: options.has_head ?? true, - // top: options.top ?? undefined, - // left: options.left ?? undefined, - // width: options.width ?? undefined, - // height: options.height ?? undefined, - // is_resizable: options.is_resizable ?? undefined, - // window_css: options.window_css ?? undefined, top: options.maximized ? 0 : undefined, left: options.maximized ? 0 : undefined, height: options.maximized ? `calc(100% - ${window.taskbar_height + window.toolbar_height + 1}px)` : undefined, @@ -2243,63 +2236,6 @@ window.open_item = async function(options){ } } -/** - * Returns a context menu item to create a new file/folder. - * - * @param {string} dirname - The directory path to create the item in - * @param {HTMLElement} append_to_element - Element to append the new item to - * @returns {Object} The context menu item object - */ - -window.new_context_menu_item = function(dirname, append_to_element){ - return { - html: "New", - items: [ - // New Folder - { - html: "New Folder", - icon: ``, - onClick: function(){ - create_folder(dirname, append_to_element); - } - }, - // divider - '-', - // Text Document - { - html: `Text Document`, - icon: ``, - onClick: async function(){ - create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.txt'}); - } - }, - // HTML Document - { - html: `HTML Document`, - icon: ``, - onClick: async function(){ - create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.html'}); - } - }, - // JPG Image - { - html: `JPG Image`, - icon: ``, - onClick: async function(){ - var canvas = document.createElement("canvas"); - - canvas.width = 800; - canvas.height = 600; - - canvas.toBlob((blob) =>{ - create_file({dirname: dirname, append_to_element: append_to_element, name: 'New Image.jpg', content: blob}); - }); - } - }, - ] - } -} - /** * Moves the given items to the destination path. * @@ -3127,29 +3063,6 @@ window.getUsage = () => { } -window.determine_active_container_parent = function(){ - // the container is either an ancestor of active element... - let parent_container = $(active_element).closest('.item-container'); - // ... or a descendant of it... - if(parent_container.length === 0){ - parent_container = $(active_element).find('.item-container'); - } - // ... or siblings or cousins - if(parent_container.length === 0){ - parent_container = $(active_element).closest('.window').find('.item-container'); - } - // ... or the active element itself (if it's a container) - if(parent_container.length === 0 && active_element && $(active_element).hasClass('item-container')){ - parent_container = $(active_element); - } - // ... or if there is no active element, the selected item that is not blurred - if(parent_container.length === 0 && active_item_container){ - parent_container = active_item_container; - } - - return parent_container; -} - window.getAppUIDFromOrigin = async function(origin) { try { const response = await fetch(window.api_origin + "/auth/app-uid-from-origin", { diff --git a/src/helpers/determine_active_container_parent.js b/src/helpers/determine_active_container_parent.js new file mode 100644 index 000000000..32a8388d6 --- /dev/null +++ b/src/helpers/determine_active_container_parent.js @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +const determine_active_container_parent = function(){ + // the container is either an ancestor of active element... + let parent_container = $(active_element).closest('.item-container'); + // ... or a descendant of it... + if(parent_container.length === 0){ + parent_container = $(active_element).find('.item-container'); + } + // ... or siblings or cousins + if(parent_container.length === 0){ + parent_container = $(active_element).closest('.window').find('.item-container'); + } + // ... or the active element itself (if it's a container) + if(parent_container.length === 0 && active_element && $(active_element).hasClass('item-container')){ + parent_container = $(active_element); + } + // ... or if there is no active element, the selected item that is not blurred + if(parent_container.length === 0 && active_item_container){ + parent_container = active_item_container; + } + + return parent_container; +} + +export default determine_active_container_parent; \ No newline at end of file diff --git a/src/helpers/new_context_menu_item.js b/src/helpers/new_context_menu_item.js new file mode 100644 index 000000000..28f22298c --- /dev/null +++ b/src/helpers/new_context_menu_item.js @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +/** + * Returns a context menu item to create a new folder and a variety of file types. + * + * @param {string} dirname - The directory path to create the item in + * @param {HTMLElement} append_to_element - Element to append the new item to + * @returns {Object} The context menu item object + */ + +const new_context_menu_item = function(dirname, append_to_element){ + return { + html: "New", + items: [ + // New Folder + { + html: "New Folder", + icon: ``, + onClick: function(){ + create_folder(dirname, append_to_element); + } + }, + // divider + '-', + // Text Document + { + html: `Text Document`, + icon: ``, + onClick: async function(){ + create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.txt'}); + } + }, + // HTML Document + { + html: `HTML Document`, + icon: ``, + onClick: async function(){ + create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.html'}); + } + }, + // JPG Image + { + html: `JPG Image`, + icon: ``, + onClick: async function(){ + var canvas = document.createElement("canvas"); + + canvas.width = 800; + canvas.height = 600; + + canvas.toBlob((blob) =>{ + create_file({dirname: dirname, append_to_element: append_to_element, name: 'New Image.jpg', content: blob}); + }); + } + }, + ] + } +} + +export default new_context_menu_item; \ No newline at end of file diff --git a/src/initgui.js b/src/initgui.js index 2b9392445..eada45ffc 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -33,6 +33,7 @@ import UIWindowChangeUsername from './UI/UIWindowChangeUsername.js'; import update_last_touch_coordinates from './helpers/update_last_touch_coordinates.js'; import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js'; import PuterDialog from './UI/PuterDialog.js'; +import determine_active_container_parent from './helpers/determine_active_container_parent.js'; window.initgui = async function(){ let url = new URL(window.location); From 86080b0ccf60776326008e94abb7a316caa030b5 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sun, 17 Mar 2024 16:01:06 -0700 Subject: [PATCH 21/24] Add i18n for English, Korean, and Chinese This is WIP. Help make the translations better. --- src/UI/PuterDialog.js | 8 +- src/UI/UIDesktop.js | 46 +- src/UI/UIItem.js | 65 ++- src/UI/UIPrompt.js | 8 +- src/UI/UITaskbar.js | 8 +- src/UI/UITaskbarItem.js | 12 +- src/UI/UIWindow.js | 52 +-- src/UI/UIWindowChangePassword.js | 12 +- src/UI/UIWindowChangeUsername.js | 10 +- src/UI/UIWindowClaimReferral.js | 6 +- src/UI/UIWindowColorPicker.js | 4 +- src/UI/UIWindowConfirmDownload.js | 10 +- src/UI/UIWindowCopyProgress.js | 4 +- src/UI/UIWindowDesktopBGSettings.js | 28 +- src/UI/UIWindowDownloadDirProg.js | 2 +- src/UI/UIWindowDownloadProgress.js | 2 +- src/UI/UIWindowEmailConfirmationRequired.js | 4 +- src/UI/UIWindowFeedback.js | 8 +- src/UI/UIWindowFontPicker.js | 2 +- src/UI/UIWindowGetCopyLink.js | 6 +- src/UI/UIWindowItemProperties.js | 6 +- src/UI/UIWindowLogin.js | 10 +- src/UI/UIWindowMoveProgress.js | 2 +- src/UI/UIWindowMyWebsites.js | 13 +- src/UI/UIWindowNewFolderProgress.js | 2 +- src/UI/UIWindowNewPassword.js | 6 +- src/UI/UIWindowProgressEmptyTrash.js | 4 +- src/UI/UIWindowPublishWebsite.js | 6 +- src/UI/UIWindowQR.js | 2 +- src/UI/UIWindowRecoverPassword.js | 6 +- src/UI/UIWindowRefer.js | 8 +- src/UI/UIWindowSaveAccount.js | 16 +- src/UI/UIWindowSessionList.js | 6 +- src/UI/UIWindowSignup.js | 14 +- src/UI/UIWindowUploadProgress.js | 6 +- src/helpers.js | 12 +- src/helpers/new_context_menu_item.js | 10 +- src/i18n/i18n.js | 477 ++++++++++++++++++++ src/initgui.js | 4 + src/static-assets.js | 1 + 40 files changed, 688 insertions(+), 220 deletions(-) create mode 100644 src/i18n/i18n.js diff --git a/src/UI/PuterDialog.js b/src/UI/PuterDialog.js index 648ad0c2d..21ffc8220 100644 --- a/src/UI/PuterDialog.js +++ b/src/UI/PuterDialog.js @@ -26,11 +26,11 @@ async function PuterDialog(options) {

This website uses Puter to bring you safe, secure, and private AI and Cloud features.

- - + +
-

Powered by Puter.js

-

By clicking 'Continue' you agree to Puter's Terms of Service and Privacy Policy.

+

${i18n('powered_by_puter_js')}

+

${i18n('tos_fineprint')}

`; const el_window = await UIWindow({ diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index c25b69fd2..db791a299 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -622,10 +622,10 @@ async function UIDesktop(options){ // Sort by // ------------------------------------------- { - html: "Sort by", + html: i18n('sort_by'), items: [ { - html: `Name`, + html: i18n('name'), icon: $(el_desktop).attr('data-sort_by') === 'name' ? '✓' : '', onClick: async function(){ sort_items(el_desktop, 'name', $(el_desktop).attr('data-sort_order')); @@ -633,7 +633,7 @@ async function UIDesktop(options){ } }, { - html: `Date modified`, + html: i18n('date_modified'), icon: $(el_desktop).attr('data-sort_by') === 'modified' ? '✓' : '', onClick: async function(){ sort_items(el_desktop, 'modified', $(el_desktop).attr('data-sort_order')); @@ -641,7 +641,7 @@ async function UIDesktop(options){ } }, { - html: `Type`, + html: i18n('type'), icon: $(el_desktop).attr('data-sort_by') === 'type' ? '✓' : '', onClick: async function(){ sort_items(el_desktop, 'type', $(el_desktop).attr('data-sort_order')); @@ -649,7 +649,7 @@ async function UIDesktop(options){ } }, { - html: `Size`, + html: i18n('size'), icon: $(el_desktop).attr('data-sort_by') === 'size' ? '✓' : '', onClick: async function(){ sort_items(el_desktop, 'size', $(el_desktop).attr('data-sort_order')); @@ -661,7 +661,7 @@ async function UIDesktop(options){ // ------------------------------------------- '-', { - html: `Ascending`, + html: i18n('ascending'), icon: $(el_desktop).attr('data-sort_order') === 'asc' ? '✓' : '', onClick: async function(){ const sort_by = $(el_desktop).attr('data-sort_by') @@ -670,7 +670,7 @@ async function UIDesktop(options){ } }, { - html: `Descending`, + html: i18n('descending'), icon: $(el_desktop).attr('data-sort_order') === 'desc' ? '✓' : '', onClick: async function(){ const sort_by = $(el_desktop).attr('data-sort_by') @@ -684,7 +684,7 @@ async function UIDesktop(options){ // Refresh // ------------------------------------------- { - html: "Refresh", + html: i18n('refresh'), onClick: function(){ refresh_item_container(el_desktop); } @@ -693,7 +693,8 @@ async function UIDesktop(options){ // Show/Hide hidden files // ------------------------------------------- { - html: `${window.user_preferences.show_hidden_files ? 'Hide' : 'Show'} hidden files`, + html: i18n('show_hidden'), + icon: window.user_preferences.show_hidden_files ? '✓' : '', onClick: function(){ window.mutate_user_preferences({ show_hidden_files : !window.user_preferences.show_hidden_files, @@ -717,7 +718,7 @@ async function UIDesktop(options){ // Paste // ------------------------------------------- { - html: "Paste", + html: i18n('paste'), disabled: clipboard.length > 0 ? false : true, onClick: function(){ if(clipboard_op === 'copy') @@ -730,7 +731,7 @@ async function UIDesktop(options){ // Undo // ------------------------------------------- { - html: "Undo", + html: i18n('undo'), disabled: actions_history.length > 0 ? false : true, onClick: function(){ undo_last_action(); @@ -740,21 +741,12 @@ async function UIDesktop(options){ // Upload Here // ------------------------------------------- { - html: "Upload Here", + html: i18n('upload_here'), onClick: function(){ init_upload_using_dialog(el_desktop); } }, // ------------------------------------------- - // Request Files - // ------------------------------------------- - // { - // html: "Request Files", - // onClick: function(){ - // UIWindowRequestFiles({dir_path: desktop_path}) - // } - // }, - // ------------------------------------------- // - // ------------------------------------------- '-', @@ -762,7 +754,7 @@ async function UIDesktop(options){ // Change Desktop Background… // ------------------------------------------- { - html: "Change Desktop Background…", + html: i18n('change_desktop_background'), onClick: function(){ UIWindowDesktopBGSettings(); } @@ -1136,7 +1128,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // My Websites //-------------------------------------------------- { - html: "My Websites", + html: i18n('my_websites'), onClick: async function(){ UIWindowMyWebsites(); } @@ -1145,7 +1137,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // Change Username //-------------------------------------------------- { - html: "Change Username", + html: i18n('change_username'), onClick: async function(){ UIWindowChangeUsername(); } @@ -1155,7 +1147,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // Change Password //-------------------------------------------------- { - html: "Change Password", + html: i18n('change_password'), onClick: async function(){ UIWindowChangePassword(); } @@ -1164,7 +1156,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // Contact Us //-------------------------------------------------- { - html: "Contact Us", + html: i18n('contact_us'), onClick: async function(){ UIWindowFeedback(); } @@ -1178,7 +1170,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // Log Out //-------------------------------------------------- { - html: "Log Out", + html: i18n('log_out'), onClick: async function(){ // see if there are any open windows, if yes notify user if($('.window-app').length > 0){ diff --git a/src/UI/UIItem.js b/src/UI/UIItem.js index e90f420f9..183fbd5bf 100644 --- a/src/UI/UIItem.js +++ b/src/UI/UIItem.js @@ -736,7 +736,7 @@ function UIItem(options){ // ------------------------------------------- if(are_trashed){ menu_items.push({ - html: "Restore", + html: i18n('restore'), onClick: function(){ $selected_items.each(function() { const ell = this; @@ -755,7 +755,7 @@ function UIItem(options){ // Donwload // ------------------------------------------- menu_items.push({ - html: 'Download', + html: i18n('Download'), onClick: async function(){ let items = []; for (let index = 0; index < $selected_items.length; index++) { @@ -769,7 +769,7 @@ function UIItem(options){ // Zip // ------------------------------------------- menu_items.push({ - html: 'Zip', + html: i18n('zip'), onClick: async function(){ let items = []; for (let index = 0; index < $selected_items.length; index++) { @@ -788,7 +788,7 @@ function UIItem(options){ // Cut // ------------------------------------------- menu_items.push({ - html: "Cut", + html: i18n('cut'), onClick: function(){ window.clipboard_op= 'move'; window.clipboard = []; @@ -804,7 +804,7 @@ function UIItem(options){ // ------------------------------------------- if(!are_trashed){ menu_items.push({ - html: "Copy", + html: i18n('copy'), onClick: function(){ window.clipboard_op= 'copy'; window.clipboard = []; @@ -824,7 +824,7 @@ function UIItem(options){ // ------------------------------------------- if(are_trashed){ menu_items.push({ - html: 'Delete Permanently', + html: i18n('delete_permanently'), onClick: async function(){ const alert_resp = await UIAlert({ message: `Are you sure you want to permanently delete these items?`, @@ -863,7 +863,7 @@ function UIItem(options){ // ------------------------------------------- if(!are_trashed && window.feature_flags.create_shortcut){ menu_items.push({ - html: 'Create Shortcut', + html: i18n('create_shortcut'), onClick: async function(){ $selected_items.each(function() { let base_dir = path.dirname($(this).attr('data-path')); @@ -889,7 +889,7 @@ function UIItem(options){ // ------------------------------------------- if(!are_trashed){ menu_items.push({ - html: 'Delete', + html: i18n('delete'), onClick: async function(){ move_items($selected_items, trash_path); } @@ -909,7 +909,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trashed){ menu_items.push({ - html: 'Open', + html: i18n('open'), onClick: function(){ open_item({item: el_item}); } @@ -965,7 +965,7 @@ function UIItem(options){ } // add all suitable apps menu_items.push({ - html: 'Open With', + html: i18n('open_with'), items: items, }); @@ -981,7 +981,7 @@ function UIItem(options){ // ------------------------------------------- if($(el_item).closest('.window-body').length > 0 && options.is_dir){ menu_items.push({ - html: 'Open in New Window', + html: i18n('open_in_new_window'), onClick: function(){ if(options.is_dir){ open_item({item: el_item, new_window: true}) @@ -1000,7 +1000,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trashed && !is_trash && options.is_dir){ menu_items.push({ - html: 'Publish As Website', + html: i18n('publish_as_website'), disabled: !options.is_dir, onClick: async function () { if(window.require_email_verification_to_publish_website){ @@ -1027,7 +1027,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trashed && !is_trash && options.is_dir){ menu_items.push({ - html: 'Deploy As App', + html: i18n('deploy_as_app'), disabled: !options.is_dir, onClick: async function () { launch_app({ @@ -1049,19 +1049,18 @@ function UIItem(options){ // ------------------------------------------- if(is_trash){ menu_items.push({ - html: 'Empty Trash', + html: i18n('empty_trash'), onClick: async function(){ empty_trash(); } }); - } // ------------------------------------------- // Donwload // ------------------------------------------- if(!is_trash && !is_trashed && (options.associated_app_name === null || options.associated_app_name === undefined)){ menu_items.push({ - html: 'Download', + html: i18n('Download'), disabled: options.is_dir && !window.feature_flags.download_directory, onClick: async function(){ if(options.is_dir) @@ -1078,11 +1077,11 @@ function UIItem(options){ // ------------------------------------------- if(!is_trashed && !is_trash && (options.associated_app_name === null || options.associated_app_name === undefined)){ menu_items.push({ - html: 'Get Copy Link', + html: i18n('get_copy_link'), onClick: async function(){ if(window.user.is_temp && !await UIWindowSaveAccount({ - message: 'Please create an account to proceed.', + message: i18n('save_account_to_get_copy_link'), send_confirmation_code: true, window_options: { backdrop: true, @@ -1107,7 +1106,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trash && !is_trashed && !$(el_item).attr('data-path').endsWith('.zip')){ menu_items.push({ - html: "Zip", + html: i18n('zip'), onClick: function(){ zipItems(el_item, path.dirname($(el_item).attr('data-path')), false); } @@ -1118,7 +1117,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trash && !is_trashed && $(el_item).attr('data-path').endsWith('.zip')){ menu_items.push({ - html: "Unzip", + html: i18n('unzip'), onClick: async function(){ const zip = new JSZip(); let filPath = $(el_item).attr('data-path'); @@ -1146,7 +1145,7 @@ function UIItem(options){ // ------------------------------------------- if(is_trashed){ menu_items.push({ - html: 'Restore', + html: i18n('restore'), onClick: async function(){ let metadata = $(el_item).attr('data-metadata') === '' ? {} : JSON.parse($(el_item).attr('data-metadata')) move_items([el_item], path.dirname(metadata.original_path)); @@ -1163,7 +1162,7 @@ function UIItem(options){ // ------------------------------------------- if($(el_item).attr('data-immutable') === '0'){ menu_items.push({ - html: "Cut", + html: i18n('cut'), onClick: function(){ window.clipboard_op= 'move'; window.clipboard= [options.path]; @@ -1175,7 +1174,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trashed && !is_trash){ menu_items.push({ - html: "Copy", + html: i18n('copy'), onClick: function(){ window.clipboard_op= 'copy'; window.clipboard= [{path: options.path}]; @@ -1187,7 +1186,7 @@ function UIItem(options){ // ------------------------------------------- if($(el_item).attr('data-is_dir') === '1' && !is_trashed && !is_trash){ menu_items.push({ - html: "Paste Into Folder", + html: i18n('paste_into_folder'), disabled: clipboard.length > 0 ? false : true, onClick: function(){ if(clipboard_op === 'copy') @@ -1208,7 +1207,7 @@ function UIItem(options){ // ------------------------------------------- if(!is_trashed && window.feature_flags.create_shortcut){ menu_items.push({ - html: 'Create Shortcut', + html: i18n('create_shortcut'), onClick: async function(){ let base_dir = path.dirname($(el_item).attr('data-path')); // Trash on Desktop is a special case @@ -1232,7 +1231,7 @@ function UIItem(options){ // ------------------------------------------- if($(el_item).attr('data-immutable') === '0' && !is_trashed){ menu_items.push({ - html: 'Delete', + html: i18n('delete'), onClick: async function(){ move_items([el_item], trash_path); } @@ -1243,7 +1242,7 @@ function UIItem(options){ // ------------------------------------------- if(is_trashed){ menu_items.push({ - html: 'Delete Permanently', + html: i18n('delete_permanently'), onClick: async function(){ const alert_resp = await UIAlert({ message: `Are you sure you want to permanently delete this item?`, @@ -1280,7 +1279,7 @@ function UIItem(options){ // ------------------------------------------- if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_trash){ menu_items.push({ - html: "Rename", + html: i18n('rename'), onClick: function(){ activate_item_name_editor(el_item) } @@ -1294,7 +1293,7 @@ function UIItem(options){ // Properties // ------------------------------------------- menu_items.push({ - html: "Properties", + html: i18n('properties'), onClick: function(){ let window_height = 500; let window_width = 450; @@ -1387,8 +1386,8 @@ $(document).on('contextmenu', '.item-has-website-url-badge', async function(e){ items: [ // Open { - html: `Open in New Tab ` , - html_active: `Open in New Tab ` , + html: `${i18n('open_in_new_tab')} ` , + html_active: `${i18n('open_in_new_tab')} ` , onClick: function(){ const website_url = $(e.target).closest('.item').attr('data-website_url'); if(website_url){ @@ -1398,7 +1397,7 @@ $(document).on('contextmenu', '.item-has-website-url-badge', async function(e){ }, // Copy Link { - html: 'Copy Link', + html: i18n('copy_link'), onClick: async function(){ const website_url = $(e.target).closest('.item').attr('data-website_url'); if(website_url){ @@ -1499,7 +1498,7 @@ window.activate_item_name_editor= function(el_item){ } // files in trash cannot be renamed, user should be notified with an Alert. else if(path.dirname($(el_item).attr('data-path')) === window.trash_path){ - UIAlert(`This item can't be renamed because it's in the trash. To rename this item, first drag it out of the Trash.`) + UIAlert(i18n('items_in_trash_cannot_be_renamed')); return; } diff --git a/src/UI/UIPrompt.js b/src/UI/UIPrompt.js index c517b427e..333234aec 100644 --- a/src/UI/UIPrompt.js +++ b/src/UI/UIPrompt.js @@ -37,8 +37,8 @@ function UIPrompt(options){ // provide an 'OK' button if no buttons are provided if(!options.buttons || options.buttons.length === 0){ options.buttons = [ - {label: 'Cancel', value: false, type: 'default'}, - {label: 'OK', value: true, type: 'primary'}, + {label: i18n('Cancel'), value: false, type: 'default'}, + {label: i18n('OK'), value: true, type: 'primary'}, ] } @@ -52,8 +52,8 @@ function UIPrompt(options){ // buttons if(options.buttons && options.buttons.length > 0){ h += `
`; - h += ``; - h += ``; + h += ``; + h += ``; h += `
`; } diff --git a/src/UI/UITaskbar.js b/src/UI/UITaskbar.js index 0ab89bb86..9c2a5ac63 100644 --- a/src/UI/UITaskbar.js +++ b/src/UI/UITaskbar.js @@ -50,7 +50,7 @@ async function UITaskbar(options){ //--------------------------------------------- UITaskbarItem({ icon: window.icons['start.svg'], - name: 'Start', + name: i18n('start'), sortable: false, keep_in_taskbar: true, disable_context_menu: true, @@ -95,7 +95,7 @@ async function UITaskbar(options){ // ------------------------------------------- if(launch_apps.recent.length > 0){ // heading - apps_str += `

Recent

`; + apps_str += `

${i18n('recent')}

`; // apps apps_str += `
`; @@ -116,7 +116,7 @@ async function UITaskbar(options){ if(launch_apps.recommended.length > 0){ // heading apps_str += `

Recommended

`; - + // apps apps_str += ``; const el_window = await UIWindow({ @@ -93,7 +93,7 @@ async function UIWindowChangePassword(){ return; } else if(new_password !== confirm_new_password){ - $(el_window).find('.form-error-msg').html('`New Password` and `Confirm New Password` do not match.'); + $(el_window).find('.form-error-msg').html(i18n('passwords_do_not_match')); $(el_window).find('.form-error-msg').fadeIn(); return; } @@ -113,7 +113,7 @@ async function UIWindowChangePassword(){ new_pass: new_password, }), success: function (data){ - $(el_window).find('.form-success-msg').html('Password changed successfully.'); + $(el_window).find('.form-success-msg').html(i18n('password_changed')); $(el_window).find('.form-success-msg').fadeIn(); $(el_window).find('input').val(''); }, diff --git a/src/UI/UIWindowChangeUsername.js b/src/UI/UIWindowChangeUsername.js index 817bfef8f..4fa2bb7e1 100644 --- a/src/UI/UIWindowChangeUsername.js +++ b/src/UI/UIWindowChangeUsername.js @@ -30,16 +30,16 @@ async function UIWindowChangeUsername(){ h += `
`; // new username h += `
`; - h += ``; + h += ``; h += ``; h += `
`; // Change Username - h += ``; + h += ``; h += `
`; const el_window = await UIWindow({ - title: 'Change Username', + title: i18n('change_username'), app: 'change-username', single_instance: true, icon: null, @@ -78,7 +78,7 @@ async function UIWindowChangeUsername(){ const new_username = $(el_window).find('.new-username').val(); if(!new_username){ - $(el_window).find('.form-error-msg').html('All fields are required.'); + $(el_window).find('.form-error-msg').html(i18n('all_fields_required')); $(el_window).find('.form-error-msg').fadeIn(); return; } @@ -102,7 +102,7 @@ async function UIWindowChangeUsername(){ new_username: new_username, }), success: function (data){ - $(el_window).find('.form-success-msg').html('Username updated successfully.'); + $(el_window).find('.form-success-msg').html(i18n('username_changed')); $(el_window).find('.form-success-msg').fadeIn(); $(el_window).find('input').val(''); // update auth data diff --git a/src/UI/UIWindowClaimReferral.js b/src/UI/UIWindowClaimReferral.js index d7715f7c9..68f558e1f 100644 --- a/src/UI/UIWindowClaimReferral.js +++ b/src/UI/UIWindowClaimReferral.js @@ -26,9 +26,9 @@ async function UIWindowClaimReferral(options){ h += `
`; h += `
×
`; h += ``; - h += `

You have been referred to Puter by a friend!

`; - h += `

Create an account and confirm your email address to receive 1 GB of free storage. Your friend will get 1 GB of free storage too.

`; - h += ``; + h += `

${i18n('you_have_been_referred_to_puter_by_a_friend')}

`; + h += `

${i18n('confirm_account_for_free_referral_storage_c2a')}

`; + h += ``; h += `
`; const el_window = await UIWindow({ diff --git a/src/UI/UIWindowColorPicker.js b/src/UI/UIWindowColorPicker.js index a8d351213..400adbb3c 100644 --- a/src/UI/UIWindowColorPicker.js +++ b/src/UI/UIWindowColorPicker.js @@ -42,13 +42,13 @@ async function UIWindowColorPicker(options){ h += ``; // Select button - h += `` + h += `` h += ``; h += ``; h += ``; const el_window = await UIWindow({ - title: 'Select color…', + title: i18n('select_color'), app: 'color-picker', single_instance: true, icon: null, diff --git a/src/UI/UIWindowConfirmDownload.js b/src/UI/UIWindowConfirmDownload.js index b3814260f..b9605ad2e 100644 --- a/src/UI/UIWindowConfirmDownload.js +++ b/src/UI/UIWindowConfirmDownload.js @@ -34,17 +34,17 @@ async function UIWindowConfirmDownload(options){ // Item information h += `
`; // Name - h += `

Name: ${options.name ?? options.url}

`; + h += `

${i18n('name')}: ${options.name ?? options.url}

`; // Type - h += `

Type: ${options.is_dir === '1' || options.is_dir === 'true' ? 'Folder' : options.type ?? 'Unknown File Type'}

`; + h += `

${i18n('type')}: ${options.is_dir === '1' || options.is_dir === 'true' ? 'Folder' : options.type ?? 'Unknown File Type'}

`; // Source - h += `

From: ${options.source}

`; + h += `

${i18n('from')}: ${options.source}

`; h += `
`; h += ``; // Download - h += ``; + h += ``; // Cancel - h += ``; + h += ``; h +=``; const el_window = await UIWindow({ diff --git a/src/UI/UIWindowCopyProgress.js b/src/UI/UIWindowCopyProgress.js index 738da14b7..d40bda97e 100644 --- a/src/UI/UIWindowCopyProgress.js +++ b/src/UI/UIWindowCopyProgress.js @@ -29,7 +29,7 @@ async function UIWindowCopyProgress(options){ // Progress report h +=`
`; // msg - h += `Copying `; + h += `${i18n('copying')} `; h += ``; h += `
`; // progress @@ -42,7 +42,7 @@ async function UIWindowCopyProgress(options){ h += ``; const el_window = await UIWindow({ - title: `Copying`, + title: i18n('copying'), icon: window.icons[`app-icon-copying.svg`], uid: null, is_dir: false, diff --git a/src/UI/UIWindowDesktopBGSettings.js b/src/UI/UIWindowDesktopBGSettings.js index 37c38318b..3b0c98380 100644 --- a/src/UI/UIWindowDesktopBGSettings.js +++ b/src/UI/UIWindowDesktopBGSettings.js @@ -30,28 +30,28 @@ async function UIWindowDesktopBGSettings(){ h += `
`; // type - h += ``; + h += ``; h += ``; // Picture h += `
`; - h += ``; - h += ``; - h += ``; + h += ``; + h += ``; + h += ``; h += ``; h += `
` // Color h += `
`; - h += ``; + h += ``; h += `
`; h += `
`; h += `
`; @@ -69,14 +69,14 @@ async function UIWindowDesktopBGSettings(){ h += `
`; h += `
` - h += ``; - h += ``; + h += ``; + h += ``; h += `
`; h += `
`; const el_window = await UIWindow({ - title: 'Change Desktop Background…', + title: i18n('change_desktop_background'), icon: null, uid: null, is_dir: false, diff --git a/src/UI/UIWindowDownloadDirProg.js b/src/UI/UIWindowDownloadDirProg.js index c90f1e69c..8a9ba5c91 100644 --- a/src/UI/UIWindowDownloadDirProg.js +++ b/src/UI/UIWindowDownloadDirProg.js @@ -25,7 +25,7 @@ async function UIWindowDownloadDirProg(options){ let h = ''; // Loading spinner h +=`circle anim`; - h += `

${options.defaultText ?? 'Preparing...'}

`; + h += `

${options.defaultText ?? i18n('preparing')}

`; const el_window = await UIWindow({ title: 'Instant Login!', diff --git a/src/UI/UIWindowDownloadProgress.js b/src/UI/UIWindowDownloadProgress.js index 8bfd1cf09..5503a5bf8 100644 --- a/src/UI/UIWindowDownloadProgress.js +++ b/src/UI/UIWindowDownloadProgress.js @@ -29,7 +29,7 @@ async function UIWindowDownloadProgress(options){ // Progress report h +=`
`; // msg - h += `Downloading ${options.item_name ?? ''}`; + h += `${i18n('downloading')} ${options.item_name ?? ''}`; h += `
`; // Progress h += `
`; diff --git a/src/UI/UIWindowEmailConfirmationRequired.js b/src/UI/UIWindowEmailConfirmationRequired.js index 14b7aaf56..88eb9ca70 100644 --- a/src/UI/UIWindowEmailConfirmationRequired.js +++ b/src/UI/UIWindowEmailConfirmationRequired.js @@ -47,10 +47,10 @@ function UIWindowEmailConfirmationRequired(options){ h += ``; h += ``; h += `
`; - h += `Re-send Confirmation Code`; + h += `${i18n('resend_confirmation_code')}`; if(options.logout_in_footer){ h += ` • `; - h += `Log Out`; + h += `${i18n('log_out')}`; } h += `
`; h += `
`; diff --git a/src/UI/UIWindowFeedback.js b/src/UI/UIWindowFeedback.js index aaba86ce3..5e0b18f7c 100644 --- a/src/UI/UIWindowFeedback.js +++ b/src/UI/UIWindowFeedback.js @@ -28,18 +28,18 @@ async function UIWindowQR(options){ // success h += ``; // form h += ``; h += `
`; const el_window = await UIWindow({ - title: 'Contact Us', + title: i18n('contact_us'), app: 'feedback', single_instance: true, icon: null, diff --git a/src/UI/UIWindowFontPicker.js b/src/UI/UIWindowFontPicker.js index 5e1b23b81..f4208b140 100644 --- a/src/UI/UIWindowFontPicker.js +++ b/src/UI/UIWindowFontPicker.js @@ -60,7 +60,7 @@ async function UIWindowFontPicker(options){ h += ``; // Select - h += `` + h += `` h += ``; h += ``; h += ``; diff --git a/src/UI/UIWindowGetCopyLink.js b/src/UI/UIWindowGetCopyLink.js index 8f81790e0..769383f28 100644 --- a/src/UI/UIWindowGetCopyLink.js +++ b/src/UI/UIWindowGetCopyLink.js @@ -69,7 +69,11 @@ async function UIWindowGetCopyLink(options){ $(el_window).find('.window-body .downloadable-link').val(url); $(el_window).find('.window-body .share-copy-link-on-social').on('click', function(e){ - const social_links = socialLink({url: url, title: `Get a copy of '${options.name}' on Puter.com!`, description: `Get a copy of '${options.name}' on Puter.com!`}); + const social_links = socialLink({ + url: url, + title: i18n('get_a_copy_of_on_puter', options.name, false), + description: i18n('get_a_copy_of_on_puter', options.name, false), + }); let social_links_html = ``; social_links_html += `
`; diff --git a/src/UI/UIWindowItemProperties.js b/src/UI/UIWindowItemProperties.js index db9aaafec..821483b12 100644 --- a/src/UI/UIWindowItemProperties.js +++ b/src/UI/UIWindowItemProperties.js @@ -25,8 +25,8 @@ async function UIWindowItemProperties(item_name, item_path, item_uid, left, top, h += `
`; // tabs h += `
`; - h += `
General
`; - h += `
Versions
`; + h += `
${i18n('general')}
`; + h += `
${i18n('versions')}
`; h += `
`; h+= `
`; @@ -44,7 +44,7 @@ async function UIWindowItemProperties(item_name, item_path, item_uid, left, top, h += `Versions`; h += `Associated Websites`; h += ``; - h += `Access Granted To`; + h += `${i18n('access_granted_to')}`; h += ``; h += `
`; diff --git a/src/UI/UIWindowLogin.js b/src/UI/UIWindowLogin.js index c79378846..145a1f846 100644 --- a/src/UI/UIWindowLogin.js +++ b/src/UI/UIWindowLogin.js @@ -43,12 +43,12 @@ async function UIWindowLogin(options){ h += ``; // username/email h += `
`; - h += ``; + h += ``; h += ``; h += `
`; // password with conditional type based based on options.show_password h += `
`; - h += ``; + h += ``; h += ``; // show/hide icon h += ` @@ -56,15 +56,15 @@ async function UIWindowLogin(options){ `; h += `
`; // login - h += ``; + h += ``; // password recovery - h += `

Forgot password?

`; + h += `

${i18n('forgot_pass_c2a')}

`; h += ``; h += `
`; // create account link if(options.show_signup_button === undefined || options.show_signup_button){ h += `
`; - h += ``; + h += ``; h += `
`; } h += `
`; diff --git a/src/UI/UIWindowMoveProgress.js b/src/UI/UIWindowMoveProgress.js index 18cb7f9db..2b492b335 100644 --- a/src/UI/UIWindowMoveProgress.js +++ b/src/UI/UIWindowMoveProgress.js @@ -29,7 +29,7 @@ async function UIWindowMoveProgress(options){ // Progress report h +=`
`; // msg - h += `Moving `; + h += `${i18n('moving')} `; h += ``; h += `
`; // progress diff --git a/src/UI/UIWindowMyWebsites.js b/src/UI/UIWindowMyWebsites.js index e1a6c0761..543979203 100644 --- a/src/UI/UIWindowMyWebsites.js +++ b/src/UI/UIWindowMyWebsites.js @@ -90,10 +90,10 @@ async function UIWindowMyWebsites(options){ h += `

`; h += `

`; h += ``; - h += `Disassociate Folder`; + h += `${i18n('disassociate_dir')}`; h += `

`; } - h += `

No directory associated with this address.

`; + h += `

${i18n('no_dir_associated_with_site')}

`; h += ``; } $(el_window).find('.window-body').html(h); @@ -105,7 +105,7 @@ async function UIWindowMyWebsites(options){ margin-bottom: 50px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - color: #596c7c;">You haven't published any websites!

`); + color: #596c7c;">${i18n('no_websites_published')}

`); } }, Date.now() - init_ts < 1000 ? 0 : 2000); }) @@ -136,10 +136,11 @@ $(document).on('click', '.mywebsites-site-setting', function(e){ html: `Release Address`, onClick: async function(){ const alert_resp = await UIAlert({ - message: `Are you sure you want to release this address?`, + message: i18n('release_address_confirmation'), buttons:[ { - label: 'Yes, Release It', + label: i18n('yes_release_it'), + value: 'yes', type: 'primary', }, { @@ -147,7 +148,7 @@ $(document).on('click', '.mywebsites-site-setting', function(e){ }, ] }) - if(alert_resp !== 'Yes, Release It'){ + if(alert_resp !== 'yes'){ return; } diff --git a/src/UI/UIWindowNewFolderProgress.js b/src/UI/UIWindowNewFolderProgress.js index 3f01e8af5..0f04d7db2 100644 --- a/src/UI/UIWindowNewFolderProgress.js +++ b/src/UI/UIWindowNewFolderProgress.js @@ -29,7 +29,7 @@ async function UIWindowNewFolderProgress(options){ // message h +=`
`; // text - h += `Taking a little longer than usual. Please wait...`; + h += `${i18n('taking_longer_than_usual')}`; h += `
`; h +=``; h += ``; diff --git a/src/UI/UIWindowNewPassword.js b/src/UI/UIWindowNewPassword.js index 15dc3b0a1..204c07713 100644 --- a/src/UI/UIWindowNewPassword.js +++ b/src/UI/UIWindowNewPassword.js @@ -34,17 +34,17 @@ async function UIWindowNewPassword(options){ h += `
`; // new password h += `
`; - h += ``; + h += ``; h += ``; h += `
`; // confirm new password h += `
`; - h += ``; + h += ``; h += ``; h += `
`; // Change Password - h += ``; + h += ``; h += ``; const el_window = await UIWindow({ diff --git a/src/UI/UIWindowProgressEmptyTrash.js b/src/UI/UIWindowProgressEmptyTrash.js index e771fe78e..b8efabd72 100644 --- a/src/UI/UIWindowProgressEmptyTrash.js +++ b/src/UI/UIWindowProgressEmptyTrash.js @@ -29,13 +29,13 @@ async function UIWindowProgressEmptyTrash(options){ // message h +=`
`; // text - h += `Emptying the Trash...`; + h += `${i18n('emptying_trash')}`; h += `
`; h +=``; h += ``; const el_window = await UIWindow({ - title: `Creating New Folder`, + title: i18n('emptying_trash'), icon: window.icons[`app-icon-newfolder.svg`], uid: null, is_dir: false, diff --git a/src/UI/UIWindowPublishWebsite.js b/src/UI/UIWindowPublishWebsite.js index f5a0ffa4b..a0dbb9cca 100644 --- a/src/UI/UIWindowPublishWebsite.js +++ b/src/UI/UIWindowPublishWebsite.js @@ -26,7 +26,7 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di // success h += `
`; h += ``; - h += `

${target_dir_name} has been published to:

`; + h += `

${i18n('dir_published_as_website', `${target_dir_name}`)}

`; h += `

`; h += ``; h+= `
`; @@ -36,13 +36,13 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di h += `
`; // subdomain h += `
`; - h += ``; + h += ``; h += `
https://.${window.hosting_domain}
`; h += `
`; // uid h += ``; // Publish - h += `` + h += `` h += ``; h += ``; diff --git a/src/UI/UIWindowQR.js b/src/UI/UIWindowQR.js index b66136f70..b69c7a9e0 100644 --- a/src/UI/UIWindowQR.js +++ b/src/UI/UIWindowQR.js @@ -27,7 +27,7 @@ async function UIWindowQR(options){ // close button containing the multiplication sign h += `
×
`; h += `
`; - h += `

Scan the code below to log into this session from other devices

`; + h += `

${i18n('scan_qr_c2a')}

`; h += `
`; const el_window = await UIWindow({ diff --git a/src/UI/UIWindowRecoverPassword.js b/src/UI/UIWindowRecoverPassword.js index 8e37acef0..fd6591f51 100644 --- a/src/UI/UIWindowRecoverPassword.js +++ b/src/UI/UIWindowRecoverPassword.js @@ -26,13 +26,13 @@ function UIWindowRecoverPassword(options){ let h = ''; h += `
`; - h += `

Recover Password

`; + h += `

${i18n('recover_password')}

`; h += `
`; h += `

`; h += `
`; - h += ``; + h += ``; h += ``; - h += ``; + h += ``; h += `
`; h += `
`; diff --git a/src/UI/UIWindowRefer.js b/src/UI/UIWindowRefer.js index b2fa78bd9..aba0bc0ea 100644 --- a/src/UI/UIWindowRefer.js +++ b/src/UI/UIWindowRefer.js @@ -29,8 +29,8 @@ async function UIWindowRefer(options){ h += `
`; h += `
×
`; h += ``; - h += `

Get 1 GB for every friend who creates and confirms an account on Puter. Your friend will get 1 GB too!

`; - h += ``; + h += `

${i18n('refer_friends_c2a')}

`; + h += ``; h += ``; h += `` h += ``; @@ -72,11 +72,11 @@ async function UIWindowRefer(options){ $(el_window).find('.window-body .downloadable-link').val(url); $(el_window).find('.window-body .share-copy-link-on-social').on('click', function(e){ - const social_links = socialLink({url: url, title: `Get 1 GB of free storage on Puter.com!`, description: `Get 1 GB of free storage on Puter.com!`}); + const social_links = socialLink({url: url, title: i18n('refer_friends_social_media_c2a'), description: i18n('refer_friends_social_media_c2a')}); let social_links_html = ``; social_links_html += `
`; - social_links_html += `

Share to

` + social_links_html += `

${i18n('share_to')}

` social_links_html += `` social_links_html += `` social_links_html += `` diff --git a/src/UI/UIWindowSaveAccount.js b/src/UI/UIWindowSaveAccount.js index 2c5a367ca..3a57ba9dc 100644 --- a/src/UI/UIWindowSaveAccount.js +++ b/src/UI/UIWindowSaveAccount.js @@ -32,39 +32,39 @@ async function UIWindowSaveAccount(options){ // success h += ``; // form h += ``; h += `
`; diff --git a/src/UI/UIWindowSessionList.js b/src/UI/UIWindowSessionList.js index 640567fcb..fb1284524 100644 --- a/src/UI/UIWindowSessionList.js +++ b/src/UI/UIWindowSessionList.js @@ -28,16 +28,16 @@ async function UIWindowSessionList(options){ return new Promise(async (resolve) => { let h = ''; h += `
`; - h += `
Signing in...
` + h += `
${i18n('signing_in')}
` h += `
`; - h += `

Sign in with Puter

` + h += `

${i18n('sign_in_with_puter')}

` for (let index = 0; index < logged_in_users.length; index++) { const l_user = logged_in_users[index]; h += `
${l_user.username}
`; } h += `
`; - h += `
`; + h += `
`; h += `
`; const el_window = await UIWindow({ diff --git a/src/UI/UIWindowSignup.js b/src/UI/UIWindowSignup.js index 5c0e25c43..fce5df866 100644 --- a/src/UI/UIWindowSignup.js +++ b/src/UI/UIWindowSignup.js @@ -40,39 +40,39 @@ function UIWindowSignup(options){ // Form h += `
`; // title - h += `

Create Free Account

`; + h += `

${i18n('create_free_account')}

`; // signup form h += ``; h += `
`; // login link // create account link h += `
`; - h += ``; + h += ``; h += `
`; h += `
`; diff --git a/src/UI/UIWindowUploadProgress.js b/src/UI/UIWindowUploadProgress.js index 1612c93b6..e436fc748 100644 --- a/src/UI/UIWindowUploadProgress.js +++ b/src/UI/UIWindowUploadProgress.js @@ -29,19 +29,19 @@ async function UIWindowUploadProgress(options){ // Progress report h +=`
`; // msg - h += `Preparing for upload...`; + h += `${i18n('preparing_for_upload')}`; h += `
`; // progress h += `
`; h += `
`; h += `
`; // cancel - h += ``; + h += ``; h +=``; h += ``; const el_window = await UIWindow({ - title: `Upload`, + title: i18n('Upload'), icon: window.icons[`app-icon-uploader.svg`], uid: null, is_dir: false, diff --git a/src/helpers.js b/src/helpers.js index 7b0b795ed..03128b9e3 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -409,17 +409,17 @@ window.globToRegExp = function (glob, opts) { */ window.validate_fsentry_name = function(name){ if(!name) - throw {message: 'Name cannot be empty.'} + throw {message: i18n('name_cannot_be_empty')} else if(!isString(name)) - throw {message: "Name can only be a string."} + throw {message: i18n('name_must_be_string')} else if(name.includes('/')) - throw {message: "Name cannot contain the '/' character."} + throw {message: i18n('name_cannot_contain_slash')} else if(name === '.') - throw {message: "Name can not be the '.' character."}; + throw {message: i18n('name_cannot_contain_period')}; else if(name === '..') - throw {message: "Name can not be the '..' character."}; + throw {message: i18n('name_cannot_contain_double_period')}; else if(name.length > window.max_item_name_length) - throw {message: `Name can not be longer than ${config.max_item_name_length} characters`} + throw {message: i18n('name_too_long', config.max_item_name_length)} else return true } diff --git a/src/helpers/new_context_menu_item.js b/src/helpers/new_context_menu_item.js index 28f22298c..05a7eeaf9 100644 --- a/src/helpers/new_context_menu_item.js +++ b/src/helpers/new_context_menu_item.js @@ -28,11 +28,11 @@ const new_context_menu_item = function(dirname, append_to_element){ return { - html: "New", + html: i18n('new'), items: [ // New Folder { - html: "New Folder", + html: i18n('new_folder'), icon: ``, onClick: function(){ create_folder(dirname, append_to_element); @@ -42,7 +42,7 @@ const new_context_menu_item = function(dirname, append_to_element){ '-', // Text Document { - html: `Text Document`, + html: i18n('text_document'), icon: ``, onClick: async function(){ create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.txt'}); @@ -50,7 +50,7 @@ const new_context_menu_item = function(dirname, append_to_element){ }, // HTML Document { - html: `HTML Document`, + html: i18n('html_document'), icon: ``, onClick: async function(){ create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.html'}); @@ -58,7 +58,7 @@ const new_context_menu_item = function(dirname, append_to_element){ }, // JPG Image { - html: `JPG Image`, + html: i18n('jpeg_image'), icon: ``, onClick: async function(){ var canvas = document.createElement("canvas"); diff --git a/src/i18n/i18n.js b/src/i18n/i18n.js new file mode 100644 index 000000000..d69ea2cd5 --- /dev/null +++ b/src/i18n/i18n.js @@ -0,0 +1,477 @@ +window.locale = 'en'; + +window.i18n = function (key, replacements = [], encode_html = true) { + if(typeof replacements === 'boolean' && encode_html === undefined){ + encode_html = replacements; + replacements = []; + }else if(Array.isArray(replacements) === false){ + replacements = [replacements]; + } + + // if locale is not set, default to en + if(!window.translations[window.locale]) + window.locale = 'en'; + + let str = window.translations[window.locale][key]; + + if (!str) { + str = key; + } + str = encode_html ? html_encode(str) : str; + // replace %% occurrences with the values in replacements + // %% is for simple text replacements + // %strong% is for tags + // e.g. "Hello, %strong%" => "Hello, World" + // e.g. "Hello, %%" => "Hello, World" + // e.g. "Hello, %strong%, %%!" => "Hello, World, Universe!" + for (let i = 0; i < replacements.length; i++) { + // sanitize the replacement + replacements[i] = encode_html ? html_encode(replacements[i]) : replacements[i]; + // find first occurrence of %strong% + let index = str.indexOf('%strong%'); + // find first occurrence of %% + let index2 = str.indexOf('%%'); + // decide which one to replace + if (index === -1 && index2 === -1) { + break; + } else if (index === -1) { + str = str.replace('%%', replacements[i]); + } else if (index2 === -1) { + str = str.replace('%strong%', '' + replacements[i] + ''); + } else if (index < index2) { + str = str.replace('%strong%', '' + replacements[i] + ''); + } else { + str = str.replace('%%', replacements[i]); + } + } + return str; +} + +window.translations = { + en: { + access_granted_to: "Access Granted To", + add_existing_account: "Add Existing Account", + all_fields_required: 'All fields are required.', + apply: "Apply", + ascending: 'Ascending', + background: "Background", + browse: "Browse", + cancel: 'Cancel', + center: 'Center', + change_desktop_background: 'Change desktop background…', + change_password: "Change Password", + change_username: "Change Username", + close_all_windows: "Close All Windows", + color: 'Color', + confirm_account_for_free_referral_storage_c2a: 'Create an account and confirm your email address to receive 1 GB of free storage. Your friend will get 1 GB of free storage too.', + confirm_new_password: "Confirm New Password", + contact_us: "Contact Us", + contain: 'Contain', + continue: "Continue", + copy: 'Copy', + copy_link: "Copy Link", + copying: "Copying", + cover: 'Cover', + create_account: "Create Account", + create_free_account: "Create Free Account", + create_shortcut: "Create Shortcut", + current_password: "Current Password", + cut: 'Cut', + date_modified: 'Date modified', + delete: 'Delete', + delete_permanently: "Delete Permanently", + deploy_as_app: 'Deploy as app', + descending: 'Descending', + desktop_background_fit: "Fit", + dir_published_as_website: `%strong% has been published to:`, + disassociate_dir: "Disassociate Directory", + download: 'Download', + downloading: "Downloading", + email: "Email", + email_or_username: "Email or Username", + empty_trash: 'Empty Trash', + empty_trash_confirmation: `Are you sure you want to permanently delete the items in Trash?`, + emptying_trash: 'Emptying Trash…', + feedback: "Feedback", + feedback_c2a: "Please use the form below to send us your feedback, comments, and bug reports.", + feedback_sent_confirmation: "Thank you for contacting us. If you have an email associated with your account, you will hear back from us as soon as possible.", + forgot_pass_c2a: "Forgot password?", + from: "From", + general: "General", + get_a_copy_of_on_puter: `Get a copy of '%%' on Puter.com!`, + get_copy_link: 'Get Copy Link', + hide_all_windows: "Hide All Windows", + html_document: 'HTML document', + image: 'Image', + invite_link: "Invite Link", + items_in_trash_cannot_be_renamed: `This item can't be renamed because it's in the trash. To rename this item, first drag it out of the Trash.`, + jpeg_image: 'JPEG image', + keep_in_taskbar: 'Keep in Taskbar', + log_in: "Log In", + log_out: 'Log Out', + move: 'Move', + moving: "Moving", + my_websites: "My Websites", + name: 'Name', + name_cannot_be_empty: 'Name cannot be empty.', + name_cannot_contain_double_period: "Name can not be the '..' character.", + name_cannot_contain_period: "Name can not be the '.' character.", + name_cannot_contain_slash: "Name cannot contain the '/' character.", + name_must_be_string: "Name can only be a string.", + name_too_long: `Name can not be longer than %% characters.`, + new: 'New', + new_folder: 'New folder', + new_password: "New Password", + new_username: "New Username", + no_dir_associated_with_site: 'No directory associated with this address.', + no_websites_published: "You have not published any websites yet.", + ok: 'OK', + open: "Open", + open_in_new_tab: "Open in New Tab", + open_in_new_window: "Open in New Window", + open_with: "Open With", + password: "Password", + password_changed: "Password changed.", + passwords_do_not_match: '`New Password` and `Confirm New Password` do not match.', + paste: 'Paste', + paste_into_folder: "Paste Into Folder", + pick_name_for_website: "Pick a name for your website:", + picture: "Picture", + powered_by_puter_js: `Powered by Puter.js`, + preparing: "Preparing...", + preparing_for_upload: "Preparing for upload...", + properties: "Properties", + properties: 'Properties', + publish: "Publish", + publish_as_website: 'Publish as website', + recent: "Recent", + recover_password: "Recover Password", + refer_friends_c2a: "Get 1 GB for every friend who creates and confirms an account on Puter. Your friend will get 1 GB too!", + refer_friends_social_media_c2a: `Get 1 GB of free storage on Puter.com!`, + refresh: 'Refresh', + release_address_confirmation: `Are you sure you want to release this address?`, + remove_from_taskbar:'Remove from Taskbar', + rename: 'Rename', + repeat: 'Repeat', + resend_confirmation_code: "Re-send Confirmation Code", + restore: "Restore", + save_account_to_get_copy_link: "Please create an account to proceed.", + save_account_to_publish: 'Please create an account to proceed.', + save_session_c2a: 'Create an account to save your current session and avoid losing your work.', + scan_qr_c2a: 'Scan the code below to log into this session from other devices', + select: "Select", + select_color: 'Select color…', + send: "Send", + send_password_recovery_email: "Send Password Recovery Email", + session_saved: "Thank you for creating an account. This session has been saved.", + set_new_password: "Set New Password", + share_to: "Share to", + show_all_windows: "Show All Windows", + show_hidden: 'Show hidden', + sign_in_with_puter: "Sign in with Puter", + sign_up: "Sign Up", + signing_in: "Signing in…", + size: 'Size', + sort_by: 'Sort by', + start: 'Start', + taking_longer_than_usual: 'Taking a little longer than usual. Please wait...', + text_document: 'Text document', + tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's Terms of Service and Privacy Policy.`, + trash: 'Trash', + type: 'Type', + undo: 'Undo', + unzip: "Unzip", + upload: 'Upload', + upload_here: 'Upload here', + username: "Username", + username_changed: 'Username updated successfully.', + versions: "Versions", + yes_release_it: 'Yes, Release It', + you_have_been_referred_to_puter_by_a_friend: "You have been referred to Puter by a friend!", + zip: "Zip", + }, + // korean + ko: { + access_granted_to: "접근 권한 부여", + add_existing_account: "기존 계정 추가", + all_fields_required: '모든 필드는 필수입니다.', + apply: "적용", + ascending: '오름차순', + background: "배경", + browse: "찾아보기", + cancel: '취소', + center: '중앙', + change_desktop_background: '바탕 화면 배경 변경…', + change_password: "비밀번호 변경", + change_username: "사용자 이름 변경", + close_all_windows: "모든 창 닫기", + color: '색상', + confirm_account_for_free_referral_storage_c2a: '계정을 생성하고 이메일 주소를 확인하여 1GB의 무료 저장 공간을 받으십시오. 친구도 1GB의 무료 저장 공간을 받게 됩니다.', + confirm_new_password: "새 비밀번호 확인", + contact_us: "문의하기", + contain: '포함', + continue: "계속", + copy: '복사', + copy_link: "링크 복사", + copying: "복사 중", + cover: '표지', + create_account: "계정 생성", + create_free_account: "무료 계정 생성", + create_shortcut: "바로 가기 만들기", + current_password: "현재 비밀번호", + cut: '잘라내기', + date_modified: '수정한 날짜', + delete: '삭제', + delete_permanently: "영구 삭제", + deploy_as_app: '앱으로 배포', + descending: '내림차순', + desktop_background_fit: "맞추기", + dir_published_as_website: `%strong% 다음에 게시되었습니다:`, + disassociate_dir: "디렉토리 연결 해제", + download: '다운로드', + downloading: "다운로드 중", + email: "이메일", + email_or_username: "이메일 또는 사용자 이름", + empty_trash: '휴지통 비우기', + empty_trash_confirmation: `휴지통의 항목을 영구적으로 삭제하시겠습니까?`, + emptying_trash: '휴지통 비우는 중…', + feedback: "피드백", + feedback_c2a: "아래 양식을 사용하여 피드백, 의견 및 버그 보고를 보내십시오.", + feedback_sent_confirmation: "문의해 주셔서 감사합니다. 계정에 이메일이 연결되어 있으면 가능한 빨리 회신 드리겠습니다.", + forgot_pass_c2a: "비밀번호를 잊으셨나요?", + from: "보낸 사람", + general: "일반", + get_a_copy_of_on_puter: `Puter.com에서 '%%'의 사본을 받으세요!`, + get_copy_link: '링크 복사', + hide_all_windows: "모든 창 숨기기", + html_document: 'HTML 문서', + image: '이미지', + invite_link: "초대 링크", + items_in_trash_cannot_be_renamed: `이 항목은 휴지통에 있기 때문에 이름을 바꿀 수 없습니다. 이 항목의 이름을 바꾸려면 먼저 휴지통에서 끌어내십시오.`, + jpeg_image: 'JPEG 이미지', + keep_in_taskbar: '작업 표시줄에 유지', + log_in: "로그인", + log_out: '로그아웃', + move: '이동', + moving: "이동 중", + my_websites: "내 웹사이트", + name: '이름', + name_cannot_be_empty: '이름은 비워둘 수 없습니다.', + name_cannot_contain_double_period: "이름은 '..' 문자일 수 없습니다.", + name_cannot_contain_period: "이름은 '.' 문자일 수 없습니다.", + name_cannot_contain_slash: "이름에 '/' 문자를 포함할 수 없습니다.", + name_must_be_string: "이름은 문자열만 가능합니다.", + name_too_long: `이름은 %%자보다 길 수 없습니다.`, + new: '새로운', + new_folder: '새 폴더', + new_password: "새 비밀번호", + new_username: "새 사용자 이름", + no_dir_associated_with_site: '이 주소에 연결된 디렉토리가 없습니다.', + no_websites_published: "아직 웹사이트를 게시하지 않았습니다.", + ok: '확인', + open: "열기", + open_in_new_tab: "새 탭에서 열기", + open_in_new_window: "새 창에서 열기", + open_with: "열기 방법", + password: "비밀번호", + password_changed: "비밀번호가 변경되었습니다.", + passwords_do_not_match: '`새 비밀번호`와 `새 비밀번호 확인`이 일치하지 않습니다.', + paste: '붙여넣기', + paste_into_folder: "폴더에 붙여넣기", + pick_name_for_website: "웹사이트 이름을 선택하세요:", + picture: "사진", + powered_by_puter_js: `Powered by Puter.js`, + preparing: "준비 중...", + preparing_for_upload: "업로드 준비 중...", + properties: "속성", + publish: "게시", + publish_as_website: '웹사이트로 게시', + recent: "최근", + recover_password: "비밀번호 찾기", + refer_friends_c2a: "Puter에서 계정을 생성하고 확인한 친구마다 1GB를 받으십시오. 친구도 1GB를 받게 됩니다!", + refer_friends_social_media_c2a: `Puter.com에서 1GB의 무료 저장 공간을 받으십시오!`, + refresh: '새로 고침', + release_address_confirmation: `이 주소를 해제하시겠습니까?`, + remove_from_taskbar:'작업 표시줄에서 제거', + rename: '이름 바꾸기', + repeat: '반복', + resend_confirmation_code: "확인 코드 다시 보내기", + restore: "복원", + save_account_to_get_copy_link: "계속하려면 계정을 생성하십시오.", + save_account_to_publish: '계속하려면 계정을 생성하십시오.', + save_session_c2a: '현재 세션을 저장하고 작업을 잃지 않으려면 계정을 생성하십시오.', + scan_qr_c2a: '다른 기기에서 이 세션으로 로그인하려면 아래 코드를 스캔하십시오', + select: "선택", + select_color: '색상 선택…', + send: "보내기", + send_password_recovery_email: "비밀번호 복구 이메일 보내기", + session_saved: "계정을 생성해 주셔서 감사합니다. 이 세션이 저장되었습니다.", + set_new_password: "새 비밀번호 설정", + share_to: "공유", + show_all_windows: "모든 창 표시", + show_hidden: '숨김 항목 표시', + sign_in_with_puter: "Puter로 로그인", + sign_up: "가입", + signing_in: "로그인 중…", + size: '크기', + sort_by: '정렬 기준', + start: '시작', + taking_longer_than_usual: '보통보다 조금 더 오래 걸립니다. 잠시만 기다려 주십시오...', + text_document: '텍스트 문서', + tos_fineprint: `무료 계정 생성을 클릭하면 Puter의 서비스 약관개인정보 보호정책에 동의하는 것입니다.`, + trash: '휴지통', + type: '유형', + undo: '실행 취소', + unzip: "압축 해제", + upload: '업로드', + upload_here: '여기에 업로드', + username: "사용자 이름", + username_changed: '사용자 이름이 성공적으로 업데이트되었습니다.', + versions: "버전", + yes_release_it: '예, 해제합니다', + you_have_been_referred_to_puter_by_a_friend: "친구가 Puter로 추천했습니다!", + zip: "압축", + }, + zh: { + access_granted_to: "访问授权给", + add_existing_account: "添加现有帐户", + all_fields_required: '所有字段都是必需的。', + apply: "应用", + ascending: '升序', + background: "背景", + browse: "浏览", + cancel: '取消', + center: '中心', + change_desktop_background: '更改桌面背景…', + change_password: "更改密码", + change_username: "更改用户名", + close_all_windows: "关闭所有窗口", + color: '颜色', + confirm_account_for_free_referral_storage_c2a: '创建帐户并确认您的电子邮件地址,以获得1 GB的免费存储空间。您的朋友也将获得1 GB的免费存储空间。', + confirm_new_password: "确认新密码", + contact_us: "联系我们", + contain: '包含', + continue: "继续", + copy: '复制', + copy_link: "复制链接", + copying: "复制", + cover: '封面', + create_account: "创建帐户", + create_free_account: "创建免费帐户", + create_shortcut: "创建快捷方式", + current_password: "当前密码", + cut: '剪切', + date_modified: '修改日期', + delete: '删除', + delete_permanently: "永久删除", + deploy_as_app: '部署为应用', + descending: '降序', + desktop_background_fit: "适合", + dir_published_as_website: `%strong% 已发布到:`, + disassociate_dir: "取消关联目录", + download: '下载', + downloading: "下载", + email: "电子邮件", + email_or_username: "电子邮件或用户名", + empty_trash: '清空回收站', + empty_trash_confirmation: `您确定要永久删除回收站中的项目吗?`, + emptying_trash: '清空回收站…', + feedback: "反馈", + feedback_c2a: "请使用下面的表格向我们发送您的反馈、评论和错误报告。", + feedback_sent_confirmation: "感谢您与我们联系。如果您的帐户关联有电子邮件,我们会尽快回复您。", + forgot_pass_c2a: "忘记密码?", + from: "从", + general: "一般", + get_a_copy_of_on_puter: `在 Puter.com 上获取 '%%' 的副本!`, + get_copy_link: '获取复制链接', + hide_all_windows: "隐藏所有窗口", + html_document: 'HTML 文档', + image: '图像', + invite_link: "邀请链接", + items_in_trash_cannot_be_renamed: `此项目无法重命名,因为它在回收站中。要重命名此项目,请先将其拖出回收站。`, + jpeg_image: 'JPEG 图像', + keep_in_taskbar: '保持在任务栏', + log_in: "登录", + log_out: '登出', + move: '移动', + moving: "移动", + my_websites: "我的网站", + name: '名称', + name_cannot_be_empty: '名称不能为空。', + name_cannot_contain_double_period: "名称不能是'..'字符。", + name_cannot_contain_period: "名称不能是'.'字符。", + name_cannot_contain_slash: "名称不能包含'/'字符。", + name_must_be_string: "名称只能是字符串。", + name_too_long: `名称不能超过 %% 个字符。`, + new: '新', + new_folder: '新文件夹', + new_password: "新密码", + new_username: "新用户名", + no_dir_associated_with_site: '此地址没有关联的目录。', + no_websites_published: "您尚未发布任何网站。", + ok: '好的', + open: "打开", + open_in_new_tab: "在新标签页中打开", + open_in_new_window: "在新窗口中打开", + open_with: "打开方式", + password: "密码", + password_changed: "密码已更改。", + passwords_do_not_match: '`新密码` 和 `确认新密码` 不匹配。', + paste: '粘贴', + paste_into_folder: "粘贴到文件夹", + pick_name_for_website: "为您的网站选择一个名称:", + picture: "图片", + powered_by_puter_js: `由 Puter.js 提供支持`, + preparing: "准备中...", + preparing_for_upload: "准备上传...", + properties: "属性", + properties: '属性', + publish: "发布", + publish_as_website: '发布为网站', + recent: "最近", + recover_password: "找回密码", + refer_friends_c2a: "每个创建并确认 Puter 帐户的朋友都会为您获得 1 GB。您的朋友也将获得 1 GB!", + refer_friends_social_media_c2a: `在 Puter.com 上获取 1 GB 的免费存储空间!`, + refresh: '刷新', + release_address_confirmation: `您确定要释放此地址吗?`, + remove_from_taskbar:'从任务栏中删除', + rename: '重命名', + repeat: '重复', + resend_confirmation_code: "重新发送确认码", + restore: "还原", + save_account_to_get_copy_link: "请创建帐户以继续。", + save_account_to_publish: '请创建帐户以继续。', + save_session_c2a: '创建帐户以保存当前会话,避免丢失工作。', + scan_qr_c2a: '扫描下面的代码以从其他设备登录此会话', + select: "选择", + select_color: '选择颜色…', + send: "发送", + send_password_recovery_email: "发送密码恢复电子邮件", + session_saved: "感谢您创建帐户。此会话已保存。", + set_new_password: "设置新密码", + share_to: "分享到", + show_all_windows: "显示所有窗口", + show_hidden: '显示隐藏', + sign_in_with_puter: "使用 Puter 登录", + sign_up: "注册", + signing_in: "登录中…", + size: '大小', + sort_by: '排序方式', + start: '开始', + taking_longer_than_usual: '需要的时间比平时长一点。请稍等...', + text_document: '文本文档', + tos_fineprint: `点击“创建免费帐户”即表示您同意 Puter 的 服务条款隐私政策。`, + trash: '回收站', + type: '类型', + undo: '撤销', + unzip: "解压缩", + upload: '上传', + upload_here: '在此上传', + username: "用户名", + username_changed: '用户名已成功更新。', + versions: "版本", + yes_release_it: '是的,释放它', + you_have_been_referred_to_puter_by_a_friend: "您已经被朋友推荐到 Puter!", + zip: "压缩", + }, +} \ No newline at end of file diff --git a/src/initgui.js b/src/initgui.js index eada45ffc..0295dcc8c 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -46,6 +46,10 @@ window.initgui = async function(){ if(window.api_origin && puter.APIOrigin !== window.api_origin) puter.setAPIOrigin(api_origin); + // determine locale + // const userLang = navigator.language || navigator.userLanguage || 'en'; + // window.locale = userLang?.split('-')[0] ?? 'en'; + window.locale = 'ko'; // Checks the type of device the user is on (phone, tablet, or desktop). // Depending on the device type, it sets a class attribute on the body tag // to style or script the page differently for each device type. diff --git a/src/static-assets.js b/src/static-assets.js index da8d50a02..481d22709 100644 --- a/src/static-assets.js +++ b/src/static-assets.js @@ -33,6 +33,7 @@ const lib_paths =[ `/lib/iro.min.js`, `/lib/isMobile.min.js`, `/lib/jszip-3.10.1.min.js`, + `/i18n/i18n.js`, ] // Ordered list of CSS stylesheets From 5b9577c31e71b472f66c6ceb5ce99fbf5719bfd1 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sun, 17 Mar 2024 16:02:46 -0700 Subject: [PATCH 22/24] set default language to English --- src/initgui.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/initgui.js b/src/initgui.js index 0295dcc8c..3166b1281 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -47,9 +47,9 @@ window.initgui = async function(){ puter.setAPIOrigin(api_origin); // determine locale - // const userLang = navigator.language || navigator.userLanguage || 'en'; - // window.locale = userLang?.split('-')[0] ?? 'en'; - window.locale = 'ko'; + const userLang = navigator.language || navigator.userLanguage || 'en'; + window.locale = userLang?.split('-')[0] ?? 'en'; + // Checks the type of device the user is on (phone, tablet, or desktop). // Depending on the device type, it sets a class attribute on the body tag // to style or script the page differently for each device type. From 8bcaf5203da30ff2dbf228f0f046efdd8a49d443 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sun, 17 Mar 2024 18:15:15 -0700 Subject: [PATCH 23/24] Update i18n.js to remove redundant words --- src/i18n/i18n.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/i18n/i18n.js b/src/i18n/i18n.js index d69ea2cd5..f31e403d4 100644 --- a/src/i18n/i18n.js +++ b/src/i18n/i18n.js @@ -141,7 +141,6 @@ window.translations = { preparing: "Preparing...", preparing_for_upload: "Preparing for upload...", properties: "Properties", - properties: 'Properties', publish: "Publish", publish_as_website: 'Publish as website', recent: "Recent", @@ -425,7 +424,6 @@ window.translations = { preparing: "准备中...", preparing_for_upload: "准备上传...", properties: "属性", - properties: '属性', publish: "发布", publish_as_website: '发布为网站', recent: "最近", From 798174b9930c2be9155e395648b19c0b28569431 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Sun, 17 Mar 2024 19:21:25 -0700 Subject: [PATCH 24/24] add Farsi --- src/i18n/i18n.js | 142 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/src/i18n/i18n.js b/src/i18n/i18n.js index f31e403d4..167500bde 100644 --- a/src/i18n/i18n.js +++ b/src/i18n/i18n.js @@ -189,6 +189,148 @@ window.translations = { you_have_been_referred_to_puter_by_a_friend: "You have been referred to Puter by a friend!", zip: "Zip", }, + // farsi + fa: { + access_granted_to: "دسترسی داده شده به", + add_existing_account: "افزودن حساب کاربری موجود", + all_fields_required: 'تمامی فیلدها الزامی هستند.', + apply: "اعمال", + ascending: 'صعودی', + background: "پس زمینه", + browse: "مرور", + cancel: 'لغو', + center: 'مرکز', + change_desktop_background: 'تغییر پس زمینه دسکتاپ…', + change_password: "تغییر رمز عبور", + change_username: "تغییر نام کاربری", + close_all_windows: "بستن همه پنجره ها", + color: 'رنگ', + confirm_account_for_free_referral_storage_c2a: 'حساب کاربری خود را ایجاد کرده و آدرس ایمیل خود را تأیید کنید تا 1 گیگابایت فضای ذخیره سازی رایگان دریافت کنید. دوست شما هم 1 گیگابایت فضای ذخیره سازی رایگان دریافت خواهد کرد.', + confirm_new_password: "تأیید رمز عبور جدید", + contact_us: "تماس با ما", + contain: 'شامل', + continue: "ادامه", + copy: 'کپی', + copy_link: "کپی لینک", + copying: "کپی", + cover: 'جلد', + create_account: "ایجاد حساب کاربری", + create_free_account: "ایجاد حساب کاربری رایگان", + create_shortcut: "ایجاد میانبر", + current_password: "رمز عبور فعلی", + cut: 'برش', + date_modified: 'تاریخ تغییر', + delete: 'حذف', + delete_permanently: "حذف دائمی", + deploy_as_app: 'نصب به عنوان برنامه', + descending: 'نزولی', + desktop_background_fit: "متناسب", + dir_published_as_website: `%strong% منتشر شده به:`, + disassociate_dir: "قطع ارتباط دایرکتوری", + download: 'دانلود', + downloading: "دانلود", + email: "ایمیل", + email_or_username: "ایمیل یا نام کاربری", + empty_trash: 'خالی کردن سطل زباله', + empty_trash_confirmation: `آیا از حذف دائمی موارد در سطل زباله مطمئن هستید؟`, + emptying_trash: 'خالی کردن سطل زباله…', + feedback: "بازخورد", + feedback_c2a: "لطفا از فرم زیر برای ارسال بازخورد، نظرات و گزارش خطا استفاده کنید.", + feedback_sent_confirmation: "با تشکر از تماس شما. اگر ایمیلی به حساب کاربری شما متصل است، در اسرع وقت پاسخ خواهیم داد.", + forgot_pass_c2a: "رمز عبور را فراموش کرده اید؟", + from: "از", + general: "عمومی", + get_a_copy_of_on_puter: `یک نسخه از '%%' را در Puter.com بگیرید!`, + get_copy_link: 'گرفتن لینک کپی', + hide_all_windows: "پنهان کردن همه پنجره ها", + html_document: 'سند HTML', + image: 'تصویر', + invite_link: "لینک دعوت", + items_in_trash_cannot_be_renamed: `این مورد نمی تواند تغییر نام دهد زیرا در سطل زباله است. برای تغییر نام این مورد، ابتدا آن را از سطل زباله بیرون بکشید.`, + jpeg_image: 'تصویر JPEG', + keep_in_taskbar: 'در نوار وظایف نگه دارید', + log_in: "ورود", + log_out: 'خروج', + move: 'انتقال', + moving: "انتقال", + my_websites: "وبسایت های من", + name: 'نام', + name_cannot_be_empty: 'نام نمی تواند خالی باشد.', + name_cannot_contain_double_period: "نام نمی تواند شامل '..' باشد.", + name_cannot_contain_period: "نام نمی تواند شامل '.' باشد.", + name_cannot_contain_slash: "نام نمی تواند شامل '/' باشد.", + name_must_be_string: "نام فقط می تواند یک رشته باشد.", + name_too_long: `نام نمی تواند بیشتر از %% کاراکتر باشد.`, + new: 'جدید', + new_folder: 'پوشه جدید', + new_password: "رمز عبور جدید", + new_username: "نام کاربری جدید", + no_dir_associated_with_site: 'هیچ دایرکتوری مرتبط با این آدرس وجود ندارد.', + no_websites_published: "هنوز هیچ وبسایتی منتشر نکرده اید.", + ok: 'خوب', + open: "باز کردن", + open_in_new_tab: "در تب جدید باز کن", + open_in_new_window: "در پنجره جدید باز کن", + open_with: "باز کردن با", + password: "رمز عبور", + password_changed: "رمز عبور تغییر یافت.", + passwords_do_not_match: '`رمز عبور جدید` و `تأیید رمز عبور جدید` مطابقت ندارند.', + paste: 'چسباندن', + paste_into_folder: "چسباندن در پوشه", + pick_name_for_website: "یک نام برای وبسایت خود انتخاب کنید:", + picture: "تصویر", + powered_by_puter_js: `پشتیبانی شده توسط Puter.js`, + preparing: "در حال آماده سازی...", + preparing_for_upload: "آماده سازی برای بارگذاری...", + properties: "ویژگی ها", + publish: "انتشار", + publish_as_website: 'انتشار به عنوان وبسایت', + recent: "اخیر", + recover_password: "بازیابی رمز عبور", + refer_friends_c2a: "برای هر دوستی که حساب کاربری Puter ایجاد و تأیید کند، 1 گیگابایت دریافت کنید. دوست شما هم 1 گیگابایت دریافت خواهد کرد!", + refer_friends_social_media_c2a: `1 گیگابایت فضای ذخیره سازی رایگان را در Puter.com بگیرید!`, + refresh: 'تازه کردن', + release_address_confirmation: `آیا مطمئن هستید که می خواهید این آدرس را آزاد کنید؟`, + remove_from_taskbar:'از نوار وظایف حذف کن', + rename: 'تغییر نام', + repeat: 'تکرار', + resend_confirmation_code: "ارسال مجدد کد تأیید", + restore: "بازیابی", + save_account_to_get_copy_link: "لطفا برای ادامه یک حساب کاربری ایجاد کنید.", + save_account_to_publish: 'لطفا برای ادامه یک حساب کاربری ایجاد کنید.', + save_session_c2a: 'برای ذخیره جلسه فعلی و جلوگیری از از دست دادن کار خود یک حساب کاربری ایجاد کنید.', + scan_qr_c2a: 'کد زیر را از دستگاه های دیگر اسکن کنید تا به این جلسه وارد شوید', + select: "انتخاب", + select_color: 'انتخاب رنگ…', + send: "ارسال", + send_password_recovery_email: "ارسال ایمیل بازیابی رمز عبور", + session_saved: "با تشکر از ایجاد حساب کاربری. این جلسه ذخیره شده است.", + set_new_password: "تنظیم رمز عبور جدید", + share_to: "اشتراک گذاری به", + show_all_windows: "نمایش همه پنجره ها", + show_hidden: 'نمایش مخفی', + sign_in_with_puter: "ورود با Puter", + sign_up: "ثبت نام", + signing_in: "ورود…", + size: 'اندازه', + sort_by: 'مرتب سازی بر اساس', + start: 'شروع', + taking_longer_than_usual: 'کمی بیشتر از معمول طول می کشد. لطفا صبر کنید...', + text_document: 'سند متنی', + tos_fineprint: `با کلیک بر روی 'ایجاد حساب کاربری رایگان' شما با شرایط خدمات و سیاست حفظ حریم خصوصی Puter موافقت می کنید.`, + trash: 'سطل زباله', + type: 'نوع', + undo: 'بازگشت', + unzip: "باز کردن فایل فشرده", + upload: 'بارگذاری', + upload_here: 'اینجا بارگذاری کنید', + username: "نام کاربری", + username_changed: 'نام کاربری با موفقیت به روز شد.', + versions: "نسخه ها", + yes_release_it: 'بله، آن را آزاد کن', + you_have_been_referred_to_puter_by_a_friend: "شما توسط یک دوست به Puter معرفی شده اید!", + zip: "فشرده سازی", + }, // korean ko: { access_granted_to: "접근 권한 부여",