Can it lead to tooth decay?

هل يمكن أن تؤدي لتسوس الأسنان؟

نعم، يمكن أن تؤدي سوء العناية بالأسنان إلى تسوس الأسنان. عندما لا يتم الاهتمام بنظافة الأسنان بشكل جيد،
يتراكم البلاك (الطبقة اللاصقة المكونة من البكتيريا والفضلات الغذائية) على سطح الأسنان. تتفاعل البكتيريا في البلاك مع السكريات الموجودة في الطعام والمشروبات لإنتاج حمض يهاجم طبقة المينا (المادة الصلبة الخارجية للأسنان)، مما يؤدي إلى تكون تسوس الأسنان.

لتجنب تسوس الأسنان، ينصح باتباع الإجراءات التالية:

  • تنظيف الأسنان بفرشاة الأسنان واستخدام خيط الأسنان يوميًا لإزالة البلاك والفضلات الغذائية.
  • تقليل تناول السكريات والمشروبات الغازية السكرية.
  • زيارة طبيب الأسنان بانتظام لفحص الأسنان وإجراء تنظيف دوري وعلاج أي مشاكل مبكرًا.

معجون اسنان

مصادر:

  • Mayo Clinic: “Cavities and tooth decay – Symptoms and causes”
  • موقع الصحة العالمية: “تسوس الأسنان”
  • موقع الجمعية الأمريكية لأطباء الأسنان: “تسوس الأسنان والوقاية منه”

API OU Banco de Dados [closed]

Estou com uma dúvida seria.

Desenvolvi um site para uma academia, onde a pessoa tem a opção de fazer uma “MATRICULA” , mas não estou sabendo a forma correta de armazenar estes dados, estou em dúvida entre uma API ou um BANCO DE DADOS em Mysql.

Se alguém poder me pontuar o motivo para usar a opção a qual sugerir.

Não ententei, apenas avaliando as possibilidades.

Why is it when throwing custom error, my error type returns object

I have a nextjs app sample that sends a request but when trying to catch an error and checking if its my custom type it returns false, and when checking its err.name returns object.

page.tsx

'use client'

...some code
handleSumbit() {

  try {
    const res = await create(values);
    console.log(res)
  } catch (err: unknown) {
   console.log(typeof err, err instanceof ResponseError) <--- returns   object, false

   if(err instanceof ResponseError) {
     if(err.reponse.status === 400) {
      // do something with the bad request here
     }
   }

   }

}

...some code

CREATE method Requests.ts

'use server'

 export async function create(data: DATA) {

   const res = await fetch(`${BASE_URL}/magic`, {
        method: "POST",
        headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${acc_tok?.value}`,
        },
        body: JSON.stringify(data),
   });

  if (!res.ok) throw new ResponseError('Bad fetch response', res) <---- THE TYPE IM EXPECTING

  return await res.json();

 }

responseError Class

  export class ResponseError extends Error {
  response: Response;
  constructor(message: string, res: Response) {

    super(message);
    this.name = this.constructor.name
    this.response = res
  }
}

I’m not sure if this is the behavior from nextjs but when I tried this in just react (No framework) this works fine, also tried it on the backend this works too. I mean when throwing a custom error I can still verify the type is not changing.

Discord.js Giveaway command [closed]

So in my code everything works, the only thing is that if the bot is crashed/restarted the giveaway does not work anymore, so like it does not do anything in the code anymore. If you can try to use node-cron that’s fine too but I can’t figure it out.

import { giveaway, giveaway_endDate } from "../../data/mongodb.js";
import ms from "ms";
import { v4 as uuidv4 } from 'uuid';

export const data = {
    name: "giveawaycreate",
    type: 1,
    description: "Creates a new giveaway",
    options: [
        {
            type: 3, // STRING Type
            name: "name",
            description: "Title of giveaway",
            required: true
        },
        {
            type: 4, // INTEGER Type
            name: "winners",
            description: "Number of winners",
            required: true
        },
        {
            type: 3, // STRING Type
            name: "duration",
            description: "Duration of the giveaway",
            required: true
        }
    ],
    dm_permission: false,
    default_member_permissions: 0
};

export async function execute(interaction, client) {
    await interaction.deferReply({ ephemeral: true });

    const title = interaction.options.getString("name", true);
    const winnerCount = interaction.options.getInteger("winners", true);
    const durationOption = interaction.options.getString("duration", true);
    const msDuration = await ms(durationOption);

    const endDate = new Date(Date.now() + msDuration);
    const formattedEndDate = `${endDate.getMonth() + 1}/${endDate.getDate()}/${endDate.getFullYear()}`;
    const unix = Math.floor(endDate / 1000);
    const remainingTime = `<t:${unix}:R>`;
    const giveawayId = uuidv4();

    const embed = {
        color: client.settings.color,
        footer: { text: `${winnerCount} winners | Ends at u2022 ${formattedEndDate}` },
        url: "https://www.google.com/search?q=hello+world",
        image: { url: `https://thumbs2.imgbox.com/a2/41/pNr8rADZ_t.png` },
        description: `### ${title}`,
        fields: [
            { name: "Time Remaining", value: `${remainingTime}`, inline: true },
            { name: "Hosted by", value: `${interaction.user}`, inline: true },
        ],
    };

    const hiddenEmbed = {
        description: giveawayId,
        url: "https://www.google.com/search?q=hello+world"
    };

    const giveaway_enter = {
        type: 1,
        components: [{
            type: 2,
            label: `Enter`,
            emoji: "<:tada:1230682264384311357>",
            style: 3,
            custom_id: `giveaway_enter`,
        }, {
            type: 2,
            label: `Leave`,
            style: 2,
            custom_id: `giveaway_leave`,
        }]
    };

    const originalReply = await interaction.channel?.send({ embeds: , ephemeral: false, components: [giveaway_enter] });
    await interaction.deleteReply();

    const logChannel = interaction.guild.channels.cache.get(client.settings.hrLogChannelId);
    if (!logChannel) {
        return interaction.reply({
            content: "CONFIGURATION ERROR: Check Log Channel ID!",
            ephemeral: true,
        });
    }

    const logEmbed = {
        title: "SFRA Command Logging",
        description: `${interaction.user} used **giveaway** command in <#${interaction.channel.id}>.`,
        fields: [
            { name: "Moderator", value: `${interaction.user}`, inline: false },
            { name: "Name", value: `${title}`, inline: false },
            { name: "Winners", value: `${winnerCount}`, inline: false },
            { name: "Time Remaining", value: `${remainingTime}`, inline: false },
        ],
        color: client.settings.color,
        footer: { text: `${interaction.guild.name}`, icon_url: interaction.guild.iconURL() || undefined },
    };

    logChannel.send({ embeds: [logEmbed], ephemeral: false });

    const giveawayEndDateSave = new giveaway_endDate({
        endDate: endDate,
    });
    await giveawayEndDateSave.save();

    const interval = setInterval(async () => {
        const currentDate = new Date();
        if (currentDate >= endDate) {
            clearInterval(interval);

            const winners = await selectWinnersFromDatabase(giveawayId, winnerCount);

            embed.fields.push({ name: "Winners", value: winners.map(winner => `<@${winner}>`).join('n'), inline: true });

            await originalReply.edit({ embeds: , components: [] });

            await giveaway_endDate.deleteMany({ endDate: endDate });
            await giveaway.deleteMany({ giveawayId: giveawayId });

            const winnerIds = winners.map(winner => `<@${winner}>`).join(' ');
            await interaction.channel.send(`Congratulations ${winnerIds}, you won ${title}!`);

            await clearGiveawayData(giveawayId);
        }
    }, 60000);
}

async function selectWinnersFromDatabase(giveawayId, winnerCount) {
    const participants = await giveaway.find({ giveawayId });

    shuffleArray(participants);

    return participants.slice(0, winnerCount).map(participant => participant.userId);
}

async function clearGiveawayData(giveawayId) {
    await giveaway.deleteMany({ giveawayId });
}

function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
}

Please ignore this part it wants me to put more things than code so i am going to just put a lot of “g’s”.
ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg

Await dispatch has no effect

I have to dispatch the action that makes the request to the API to create a user. The problem is that I can’t put an awiat to the dispatch because it has no effect. I am using redux toolkit for the state. What I want to achieve is that the navigate is executed only if the dispatch is carried out successfully. I leave you my code

  const onSubmit = async (data) =>{
    const userInfo = {
      username:data?.username,
      email:data?.email,
      phone:data?.phoneNumber,
      password:data?.password,
    }
    try {
      dispatch(signUp(userInfo))
      
        navigate("/signin"); // deberia ejecutarse solo si el dispatch es exitoso
  
    } catch (error) {
      console.log(error);
    }
  }

async thunk

import { createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
export const signUp = createAsyncThunk(
    "type/postData",
    async (data, thunkAPI) => {
      try {
        const response = await axios.post("http://localhost:4001/auth/register", data);
        // Si deseas obtener algo de vuelta
        return response.data;
      } catch (error) {
        if (error.response && error.response.data && error.response.data.error) {
          return thunkAPI.rejectWithValue(error.response.data.error);
        } else {
          throw new Error('Error fetching user applications: ' + error.message);
        }
      }
    }
);

the slice:

import { createSlice } from "@reduxjs/toolkit";
import { signUp } from "../actions/userActions";

const initialState = {
    userInfo:{},
    loading: false,
    error: null,
}

export const userSlice = createSlice({
    name: 'user', // Corregí el nombre del slice
    initialState,
    reducers: {
        // Aquí puedes agregar tus propias acciones si es necesario
    },
    extraReducers: (builder) => {
        builder
            .addCase(signUp.pending, (state) => {
                state.loading = true;
                state.error = null;
            })
            .addCase(signUp.fulfilled, (state) => {
                state.loading = false;
                state.error = null;
            })
            .addCase(signUp.rejected, (state, action) => {
                console.log(action);
                state.loading = false;
                state.error = action.payload;
            });
    },
});

export default userSlice.reducer;

I tried to use the await before the dispatch and it has no effect, also execute the navigate() with a .then after the dispatch and I have no results either

Why is my JS event handler not working in SVG?

I am currently making a graphing calculator in C++. I wanted to make multiple versions of said graphing calculator. This first attempt uses SVG and JS to create graphs.

When I create the file and use the SVG file I wish to display the point of the graph (where a user clicks). However, my method to display the coordinates is not working. I click the graph, and nothing happens.

Code:

struct Point {
    double x;
    double y;
};

using Function = long double (*)(long double);

double evaluate(Function func, double x) {
    return static_cast<double>(func(x));
}

void plotGraph(Function func, double minX, double maxX, double minY, double maxY, double stepSize, int SVG_SIZE, const string& hexLineColor) {
    // Calculate number of points to plot
    int numPoints = static_cast<int>((maxX - minX) / stepSize) + 1;

    // Vector to store points
    vector<Point> points(numPoints);

    // Evaluate function at each step and store points
    for (int i = 0; i < numPoints; ++i) {
        double x = minX + i * stepSize;
        double y = evaluate(func, x);
        // Check if y is NaN, if so, skip this point
        if (!isnan(y)) {
            points[i] = {x, y};
        } else {
            // Set x-coordinate of the skipped point to NaN so it's not drawn
            points[i] = {NAN, NAN};
        }
    }

    // Normalize points to fit in the specified range
    double xRange = maxX - minX;
    double yRange = maxY - minY;

    for (auto& point : points) {
        if (!std::isnan(point.x)) {
            point.x = (point.x - minX) * SVG_SIZE / xRange;
            point.y = (point.y - minY) * SVG_SIZE / yRange;
        }
    }

    // Calculate the x-position of the y-axis
    double yAxisXPos = -minX * SVG_SIZE / xRange;
    if (minX <= 0 && maxX >= 0) {
        yAxisXPos = -minX / (maxX - minX) * SVG_SIZE;
    } else if (minX > 0) {
        yAxisXPos = 0;
    } else {
        yAxisXPos = SVG_SIZE;
    }

    // Calculate the y-position of the x-axis
    double xAxisYPos;
    if (minY <= 0 && maxY >= 0) {
        xAxisYPos = maxY / (maxY - minY) * SVG_SIZE;
    } else if (minY > 0) {
        xAxisYPos = SVG_SIZE;
    } else {
        xAxisYPos = 0;
    }

    // Open SVG file for writing
    ofstream svgFile("graph.svg");

    // SVG header
    svgFile << "<svg width="" << SVG_SIZE << "" height="" << SVG_SIZE << "" xmlns="http://www.w3.org/2000/svg">" << std::endl;

    // Draw x-axis
    svgFile << "<line x1="" << yAxisXPos << "" y1="0" x2="" << yAxisXPos << "" y2="" << SVG_SIZE << "" style="stroke:black;stroke-width:1"/>" << std::endl;
    // Draw y-axis
    svgFile << "<line x1="0" y1="" << xAxisYPos << "" x2="" << SVG_SIZE << "" y2="" << xAxisYPos << "" style="stroke:black;stroke-width:1"/>" << std::endl;

    // Plot points and lines
    for (int i = 1; i < numPoints; ++i) {
        // Check if both x-coordinates are not NaN
        if (!isnan(points[i - 1].x) && !isnan(points[i].x)) {
            // Check if both y-coordinates are not NaN
            if (!isnan(points[i - 1].y) && !isnan(points[i].y)) {
                // Draw line between two points
                svgFile << "<line x1="" << points[i - 1].x << "" y1="" << SVG_SIZE - points[i - 1].y << "" "
                        << "x2="" << points[i].x << "" y2="" << SVG_SIZE - points[i].y << "" "
                        << "style="stroke:#" << hexLineColor << ";stroke-width:2"/>" << std::endl;
            }
        }
    }

    // Add event handler for clicking on points
    svgFile << "<script><![CDATA[" << std::endl;
    svgFile << "function showCoordinates(evt) {" << std::endl;
    svgFile << "    var svg = evt.target.ownerDocument;" << std::endl;
    svgFile << "    var point = svg.createSVGPoint();" << std::endl;
    svgFile << "    point.x = evt.clientX;" << std::endl;
    svgFile << "    point.y = evt.clientY;" << std::endl;
    svgFile << "    var svgPoint = point.matrixTransform(svg.getScreenCTM().inverse());" << std::endl;
    svgFile << "    var textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');" << std::endl;
    svgFile << "    textElement.setAttribute('x', svgPoint.x);" << std::endl;
    svgFile << "    textElement.setAttribute('y', svgPoint.y);" << std::endl;
    svgFile << "    textElement.setAttribute('fill', 'black');" << std::endl;
    svgFile << "    textElement.textContent = '(' + svgPoint.x.toFixed(2) + ', ' + svgPoint.y.toFixed(2) + ')';" << std::endl;
    svgFile << "    svg.appendChild(textElement);" << std::endl; // Append to the SVG root
    svgFile << "}" << std::endl;
    svgFile << "]]></script>" << std::endl;

    // Draw a transparent rectangle to capture clicks
    svgFile << "<rect width="" << SVG_SIZE << "" height="" << SVG_SIZE << "" fill="transparent" onclick="showCoordinates(evt)"/>" << std::endl;

    // Close SVG tag
    svgFile << "</svg>";

    // Close the file
    svgFile.close();

    cout << "Graph created in graph.svg" << endl;
}

I have tried changing the syntax a few times and I have completely changed my approach once (caused an error, so I won’t go into detail)

JavaScript userscripts: Checklist for making sure userscript doesn’t have security loopholes? [closed]

I am not expecting a definitive and complete fool-proof list, since security is an extremely complex issue, so that would not be realistic, certainly not in a format like this.

I am also not looking for a list of attacks: If anything, a list of things to avoid attacks.

I am aware of authoritative sources such as Mozilla’s Types of attacks and others, including entire libraries on the topic. However, those are deep descriptions of types of attacks, but certainly not focused on mere userscripts for e.g. Tampermonkey, and also not on practical tips, which is what I am specifically aiming to. Or at least I have not been able to find something like that.

One example: In this answer, @Bergi says: ...beware that it is possible for hostile webmasters to follow unsafeWindow usage back to the script's context and thus gain elevated privileges to pwn you with.

I am looking for a humble practical set of at least some top priority guidelines to be able to say “my script does not have a security loophole“. I suppose someone saying so would have gone through a reasonable set of well-known practical things. Well, I would imagine so.

So, when coding a userscript, for the likes of Tampermonkey (or other “monkeys”): what are the minimum (at least some) obvious things that I should go through, that my code should -or should not- be doing to avoid “security loopholes” (for example a webmaster attacking my script) without having to complete a Phd thesis to prove it?

Trying to set a form’s file input.value = null inside addEventListener

I’m trying to get rid of some errors in the web console of my internal site. This JS is not meant to serve as proper form validation or be security minded, it’s only done to apply a quick fix on the frontend of a completely internal site to limit the upload size and clear the form’s file input if the size exceeds 29M. I’m just curious of how to clear the input without having these errors.

...
                <input id="myFile" class="form-control" type="file" name="attachment" placeholder="Attachment"/>
        </form>
    </body>
...
...
    var myFile = document.getElementById('myFile');
    myFile.addEventListener('change', function() {
        var maxSize = myFile.files[0].size;
        if(maxSize > 29000000) {
            alert("The attachment you've selected with a file size of " + maxSize + " bytes exceeds the maxium size permitted");
            myFile.value = null;
            myFile.dispatchEvent(new Event('change'))
        }
    });
...

In the java console, when a file is selected on input id=”myFile” which is > 29000000 bytes, I get the alert, and the following on the console:

test/:137 Uncaught TypeError: Cannot read properties of undefined (reading 'size')
    at HTMLInputElement.<anonymous> (test/:137:39)
    at HTMLInputElement.<anonymous> (test/:141:20)

Everything technically “works” but it would appear that even though the condition to check for file size is an “if” and not a “while”, clearing the value inside the if block (myFile.value = null) seems to be the cause of the error. I am NOT a java-script person. What’s the correct way to check for properties on a form element and nullify that same element?

Thank you!

Google Apps Script methods returning “Proxy”

Google Apps Script is behaving really strangely for me and am wondering if anyone else has ever encountered the same issue and found a fix.

Instead of returning the objects / classes that the methods are supposed to return, they are instead returning “Proxy” objects, as you can see in the below screenshot which is from the debugger in the IDE:

Debugger image

Here is the very simple code I used to generate the example of my issue, with ‘my_url_here’ obviously replaced with the real Google Sheets URL:

function test() {
  
  const ss = SpreadsheetApp.openByUrl('my_url_here');

  Logger.log(ss);
}

I tried converting the “Proxy” to a usable object with Object.values( ) but that did not produce anything useful:

Using Object.values( )

Any assistance will be greatly appreciated!

Thanks

Encrypt AES-GCM in PHP, decrypt in Javascript

I have a message encrypted in PHP like this:

function encode(string $input): string
{
    return base64EncodeSafe(encodeOpenSsl($input));
}

function encodeOpenSsl(string $string): string
{
    if (!defined('CRYPT_KEY') || !defined('CRYPT_CIPHER')) {
        return $string;
    }

    $key = hex2bin(CRYPT_KEY);
    $ivLength = openssl_cipher_iv_length(CRYPT_CIPHER);
    do {
        $iv = openssl_random_pseudo_bytes($ivLength, $cstrong);
    } while (!$iv && !$cstrong);
    $encryptedString = $iv . openssl_encrypt($string, CRYPT_CIPHER, $key, OPENSSL_RAW_DATA, $iv, $tag);

    if (!empty($tag)) {
        $encryptedString .= '||' . $tag;
    }
    return base64_encode($encryptedString . '||' . $tag);
}

function base64EncodeSafe(string $string): string
{
    return str_replace(['/', '+'], ['_', '-'], base64_encode($string));
}

And I need to decrypt it in Javascript. This is my decryption code:

async function decode(input) {
    return await decodeOpenSsl(base64DecodeSafe(input));
}

function base64DecodeSafe(string) {
    return atob(string.replace(/_/g, '/').replace(/-/g, '+'));
}

async function decodeOpenSsl(data) {
    const key = hexStringToByteArray(CRYPT_KEY);
    const parts = atob(data).split('||');
    const ivAndEncrypted = new Uint8Array(parts[0].split('').map(c => c.charCodeAt(0)));
    const tag = new Uint8Array(parts[1].split('').map(c => c.charCodeAt(0)));
    const iv = ivAndEncrypted.slice(0, 12);
    const encrypted = ivAndEncrypted.slice(12);

    const encryptedWithTag = new Uint8Array([...encrypted, ...tag]);

    const cryptoKey = await crypto.subtle.importKey(
        'raw',
        key,
        { name: 'AES-GCM' },
        false,
        ['decrypt']
    );

    const decrypted = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: iv, additionalData: new Uint8Array(), tagLength: 128 },
        cryptoKey,
        encryptedWithTag
    );

    return new TextDecoder().decode(decrypted);
}

function hexStringToByteArray(hexString) {
    var result = [];
    while (hexString.length >= 2) {
        result.push(parseInt(hexString.substring(0, 2), 16));
        hexString = hexString.substring(2, hexString.length);
    }
    return new Uint8Array(result);
}

But I keep getting the following:

DOMException [OperationError]: The operation failed for an operation-specific reason
at AESCipherJob.onDone (node:internal/crypto/util:445:19) {
[cause]: [Error: Cipher job failed]

The error doesn’t give much info probably for security reasons, but maybe someone had a problem like this and could help. Thank you.

Ember.js octane acceptance test

I don’t understand why fillIn can’t find ‘data-test-code-nav-selector’.

I have paused the test and using the devtools I copied this DOM excerpt. It displays as expected. As you can see, the select tag has the expected attribute.

<select label="codingDropdown" class="h-full bg-gray-50 rounded shadow border py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" data-test-code-nav-selector="">
    <option value="PDF_I10_PCS_APPENDICES">ICD-10 PCS Appendices</option>
    <option value="PDF_I10_PCS_REFERENCE_MANUAL">ICD-10-PCS Reference Manual</option>
</select>

Then, in the test.js file, I’m calling fillIn with the same attribute.

await this.pauseTest();
fillIn('data-test-code-nav-selector', 'PDF_I10_PCS_REFERENCE_MANUAL');

But the test server states:

global failure: Error: Element not found when calling `fillIn('data-test-code-nav-selector')`.@ 10974 ms
Source:     
Error: Element not found when calling `fillIn('data-test-code-nav-selector')`.

The Ember.js forum states: ask your question on stackoverflow.

Running tsc on an eslint.config.js raises error

I am running tsc --skipLibCheck with the files below:

eslint.config.js:

// @ts-check
import jslint from "@eslint/js"

export default []

tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "target": "ES5",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ES2022",
    "skipLibCheck": true,
    "allowJs": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
  },
  "include": ["src"],
  "files": ["eslint.config.js"]
}

I get the following error:

eslint.config - error TS7016: Could not find a declaration file for module '@eslint/js'. './node_modules/@eslint/js/src/index.js' implicitly has an 'any' type.

How to do to ignore type check on imports?

Empty string in an array coming back undefined [closed]

I have narrowed my issue down to the following code. I don’t understand what weird javascript thing is happening here. An explanation would be greatly appreciated.

let test = ["Test"];
test.concat([""]);
console.log(test); //> ["Test", ""]
console.log(test[1]); //> "undefined"  <-Confused by this
test[0] += "1"; //> ["Test1", ""]
test[1] += "1"; //> ["Test1", "undefined1"]  <-How I found it.

As pointed out in the comments, I don’t get why the concatenation says it worked, but then I get an “undefined” instead of an empty “string”?

How do I modify friction to make a Spinning Circle stop at a specific angle?

I am creating a Spinning Wheel game in Javascript. When a button is clicked, a random velocity is set. When the stop-button is clicked, a friction is applied to the velocity. When fricition is activated, every frame this happens:

// friction = 0.98;
velocity = velocity * friction;
theAngle = theAngle + velocity;

When velocity < 0.002, the wheel will stop.

What I want to do here is, is modify the friction variable so that the Wheel stops at an angle I can pick myself.

So far I have succeeded in calculating the stopAngle, and the number of frames the wheel spins.

let angle = theAngle // the current angle (before stopping)
let speed = velocity;
let frames = 0;
let willStopAtAngle = angle;
const targetSpeed = 0.002;
while (speed > targetSpeed) {
speed *= config.friction; 
frames = frames + 1; 
angle = angle + speed; 
}

I have also calculated the angle I want the wheel to stop, and I know this is correct.

const diff = stopAngle - (angle % (Math.PI * 2));
const actualStopAngle = angle + stopAngle

I feel like I know enough, but I just don’t know how to calculate the right friction

friction = (actualStopAngle - theAngle)/frames // I thought something like this might work;

Thanks!